Cascaded Rings Filter

In this example, we will use a combination of component-level FDTD simulations and circuit simulations to build a filter based on cascaded ring resonators. They can be use to tailor the resonance spectra of a ring filter to make it flatter near the pass band as well as make the roll-off steeper in the rejection band. More details about ring-based filters can be found in [1].
References
- Geuzebroek, D.H., Driessen, A. (2006). “Ring-Resonator-Based Wavelength Filters.” In: Venghaus, H. (eds) Wavelength Filters in Fibre Optics. Springer Series in Optical Sciences, vol 123. Springer, Berlin, Heidelberg. DOI: 10.1007/3-540-31770-8_9.
import pathlib
import matplotlib.pyplot as pltimport numpy as npimport photonforge as pfimport siepic_forge as siepicimport tidy3d as tdFor the design, we will will use the SiEPIC OpenEBL technology and the its predefined strip waveguide for TE mode in the C band.
tech = siepic.ebeam()pf.config.default_technology = tech
print("Predefined waveguide profiles: " + ", ".join(tech.ports))Predefined waveguide profiles: MM_TE_1550_2000, MM_TE_1550_3000, Rib_TE_1310_350, Rib_TE_1550_500, Slot_TE_1550_500, TE-TM_1550_450, TE_1310_350, TE_1310_410, TE_1550_500, TM_1310_350, TM_1550_500, eskid_TE_1550wavelengths = np.linspace(1.548, 1.552, 81)port_spec = tech.ports["TE_1550_500"]Point Couplers
Section titled “Point Couplers”Instead of solving the full cascaded problem in FDTD, we can solve for the two point couplers (i.e. coupling between the bus waveguide to ring waveguide and coupling between two ring waveguides). Then, we will be able to utilize the computed S matrix to dynamically vary the number of rings and run fast circuit simulations.
We use parametric components to quickly design both couplers:
- ring_coupler for the bus, and
- dual_ring_coupler for the inter-ring coupling.
Note that we decrease the default mesh refinement from 20 to 15 minimal grid cells per wavelength to decrease the simulation size. That can slightly change the exact resonant frequencies of the filter, but fabrication imperfections usually have a much higher impact here, so that is not so important in this example.
However, we define a smaller shut-off condition (10⁻⁷, instead of the default 10⁻⁵) to decrease residual fields in the simulation, and we increase the default run time quality factor to ensure enough time for the shut-off condition. These measures are necessary because we will combine the frequency-domain S parameters into resonant devices, where small numerical errors can be amplified and lead to non-passive responses (identifiable, for example, if we see any S parameter magnitude above unit).
pf.config.default_mesh_refinement = 15
# Default parameters for parametric componentspf.config.default_kwargs = { "port_spec": port_spec, "radius": 10, "coupling_distance": 0.6, # measured between waveguide centers "tidy3d": { "run_time": td.RunTimeSpec(quality_factor=10), "shutoff": 1e-7, },}bus_to_ring = pf.parametric.s_bend_ring_coupler(s_bend_offset=port_spec.width)bus_to_ringring_to_ring = pf.parametric.dual_ring_coupler()ring_to_ringWe will run the S parameter simulation and plot it with our S matrix plot utility for both couplers.
fig, _ = pf.plot_s_matrix(bus_to_ring.s_matrix(pf.C_0 / wavelengths))fig.suptitle("Bus to ring")
fig, _ = pf.plot_s_matrix(ring_to_ring.s_matrix(pf.C_0 / wavelengths))_ = fig.suptitle("Ring to ring")Uploading task 'Mode-StripTE1550nmw500nm…'Uploading task 'Mode-StripTE1550nmw500nm…'Uploading task 'P1@0…'Uploading task 'X1@0…'Uploading task 'Mode-StripTE1550nmw500nm…'Uploading task 'Mode-StripTE1550nmw500nm…'Uploading task 'P3@0…'Uploading task 'X3@0…'Starting task 'P1@0': https://tidy3d.simulation.cloud/workbench?taskId=fdve-ce3c003b-4df8-452b-ba48-7759717d3720Uploading task 'P0@0…'Starting task 'X1@0': https://tidy3d.simulation.cloud/workbench?taskId=fdve-05ac15a3-7250-48da-98f6-ce305ecf42c5Starting task 'Mode-StripTE1550nmw500nm': https://tidy3d.simulation.cloud/workbench?taskId=mo-dafcc0d4-834a-4e75-8e0d-20a30cabc4edUploading task 'P1@0…'Downloading data from 'P1@0'…Starting task 'Mode-StripTE1550nmw500nm': https://tidy3d.simulation.cloud/workbench?taskId=mo-50c5fe79-526a-44d9-bcca-bae3c54db18eStarting task 'Mode-StripTE1550nmw500nm': https://tidy3d.simulation.cloud/workbench?taskId=mo-be6f62d4-3d63-45c9-b5d5-07b09c5f5964Uploading task 'P2@0…'Downloading data from 'X1@0'…Starting task 'P3@0': https://tidy3d.simulation.cloud/workbench?taskId=fdve-e6ad5266-cd7e-4cb2-a7ae-a1a002fade69Uploading task 'P3@0…'Downloading data from 'Mode-StripTE1550nmw500nm'…Starting task 'Mode-StripTE1550nmw500nm': https://tidy3d.simulation.cloud/workbench?taskId=mo-4a70e88c-3bfc-4ab2-989d-141de82489f9Starting task 'X3@0': https://tidy3d.simulation.cloud/workbench?taskId=fdve-7e699924-6946-4cab-ae62-d8a9843acfb3Downloading data from 'Mode-StripTE1550nmw500nm'…Downloading data from 'Mode-StripTE1550nmw500nm'…Downloading data from 'P3@0'…Downloading data from 'Mode-StripTE1550nmw500nm'…14:29:00 -03 WARNING: Warning messages were found in the solver log. For more information, check 'SimulationData.log' or use 'web.download_log(task_id)'.
Downloading data from 'X3@0'…Starting task 'P0@0': https://tidy3d.simulation.cloud/workbench?taskId=fdve-174c9c57-89d0-4f59-9200-79c5af3a9d52Starting task 'P3@0': https://tidy3d.simulation.cloud/workbench?taskId=fdve-55b09cad-87c2-465e-905e-7d9fe31f6da8Starting task 'P1@0': https://tidy3d.simulation.cloud/workbench?taskId=fdve-1d1e4b0f-64a8-4075-b40b-188e8d206dbb14:29:02 -03 WARNING: Warning messages were found in the solver log. For more information, check 'SimulationData.log' or use 'web.download_log(task_id)'.
Downloading data from 'P3@0'…Downloading data from 'P0@0'…Downloading data from 'P1@0'…Starting task 'P2@0': https://tidy3d.simulation.cloud/workbench?taskId=fdve-4aa7b4a6-93a6-43b9-80f7-c86ee7cb62bdDownloading data from 'P2@0'…Uploading task 'P0@0…'Uploading task 'X0@0…'Uploading task 'P1@0…'Uploading task 'X1@0…'Uploading task 'P2@0…'Uploading task 'X2@0…'Uploading task 'P3@0…'Uploading task 'X3@0…'Uploading task 'P0@0…'Uploading task 'P1@0…'Uploading task 'P2@0…'Starting task 'P0@0': https://tidy3d.simulation.cloud/workbench?taskId=fdve-b29b90e5-b463-41c7-beb8-caaf121840b8Starting task 'X1@0': https://tidy3d.simulation.cloud/workbench?taskId=fdve-af609505-f93f-4852-867f-316177a0295bUploading task 'P3@0…'Starting task 'P1@0': https://tidy3d.simulation.cloud/workbench?taskId=fdve-ed8da82c-74ab-495c-9817-8ab6d4cc2369Starting task 'X0@0': https://tidy3d.simulation.cloud/workbench?taskId=fdve-c2e3a07d-9138-4989-adc7-796ccc903989Starting task 'X2@0': https://tidy3d.simulation.cloud/workbench?taskId=fdve-c5d3c666-da85-4d8b-aa24-b4680862079aStarting task 'P2@0': https://tidy3d.simulation.cloud/workbench?taskId=fdve-91a57eaf-dae4-4182-921f-ee4f58fafa50Downloading data from 'X1@0'…Downloading data from 'P0@0'…Downloading data from 'P1@0'…Starting task 'X3@0': https://tidy3d.simulation.cloud/workbench?taskId=fdve-b290e861-3951-40f3-8483-56418ecddbdbStarting task 'P3@0': https://tidy3d.simulation.cloud/workbench?taskId=fdve-fc1c9f4b-a060-42d5-8691-7a32a0525238Downloading data from 'X0@0'…Starting task 'P2@0': https://tidy3d.simulation.cloud/workbench?taskId=fdve-baad4a46-a208-4ec3-a052-f37d4e1d2e8fDownloading data from 'X2@0'…Downloading data from 'P2@0'…Starting task 'P0@0': https://tidy3d.simulation.cloud/workbench?taskId=fdve-bbc7a5ba-b77b-4332-a88f-568e4dcf014914:29:24 -03 WARNING: Warning messages were found in the solver log. For more information, check 'SimulationData.log' or use 'web.download_log(task_id)'.
Downloading data from 'X3@0'…Downloading data from 'P3@0'…Starting task 'P1@0': https://tidy3d.simulation.cloud/workbench?taskId=fdve-eba98033-ce74-4e2d-ae8b-fa1e7db1a871Downloading data from 'P2@0'…Downloading data from 'P0@0'…Starting task 'P3@0': https://tidy3d.simulation.cloud/workbench?taskId=fdve-39a3766b-2a48-4627-810b-f0def2181a6414:29:27 -03 WARNING: Warning messages were found in the solver log. For more information, check 'SimulationData.log' or use 'web.download_log(task_id)'.
Downloading data from 'P1@0'…Downloading data from 'P3@0'…

Add drop filter
Section titled “Add drop filter”Now that we have simulated S parameters for the point couplers, we use a netlist-driven layout to create an add-drop filter and look at the resonant response of the ring. The component_from_netlist function can be used to build an add-drop filter component with a circuit model.
netlist_add_drop = { "name": "add_drop", "instances": [bus_to_ring, bus_to_ring], "connections": [((1, "P1"), (0, "P3"))], "ports": [ (0, "P0", "In"), (0, "P2", "Through"), (1, "P0", "Add"), (1, "P2", "Drop"), ], "models": [pf.CircuitModel()],}
add_drop = pf.component_from_netlist(netlist_add_drop)add_dropSince we have added a Circuit model to the add-drop filter, PhotonForge automatically uses the S parameters that were already calculated for the instance components to calculate the circuit’s S matrix. It will need to perform a few mode-solver calculations to make sure the correct phases are accounted for after instance rotations.
s_matrix_add_drop = add_drop.s_matrix(frequencies=pf.C_0 / wavelengths)_ = pf.plot_s_matrix(s_matrix_add_drop, input_ports=["In"])Uploading task 'Mode-StripTE1550nmw500nm…'Uploading task 'Mode-StripTE1550nmw500nm…'Uploading task 'Mode-StripTE1550nmw500nm…'Uploading task 'Mode-StripTE1550nmw500nm…'Starting task 'Mode-StripTE1550nmw500nm': https://tidy3d.simulation.cloud/workbench?taskId=mo-656c740a-349d-4202-b947-95ebbad1a50aStarting task 'Mode-StripTE1550nmw500nm': https://tidy3d.simulation.cloud/workbench?taskId=mo-87fed5a7-8658-410a-bd8a-12f5a1c719bfStarting task 'Mode-StripTE1550nmw500nm': https://tidy3d.simulation.cloud/workbench?taskId=mo-715c710a-7e87-4d8a-866d-4076f64bbb01Starting task 'Mode-StripTE1550nmw500nm': https://tidy3d.simulation.cloud/workbench?taskId=mo-4164b203-0871-482e-914f-d16ca2143224Downloading data from 'Mode-StripTE1550nmw500nm'…Downloading data from 'Mode-StripTE1550nmw500nm'…Downloading data from 'Mode-StripTE1550nmw500nm'…Downloading data from 'Mode-StripTE1550nmw500nm'…
Coupled Rings
Section titled “Coupled Rings”Next, we can create a coupled-ring filter to see the effect of cascading identical resonators. Again, we will use netlist helper to create the component.
netlist_coupled_rings = { "name": "cascaded_ring_filter", "instances": { "bus_coupler0": bus_to_ring, "bus_coupler1": bus_to_ring, "ring_coupler0": ring_to_ring, "ring_coupler1": ring_to_ring, }, "connections": [ (("ring_coupler0", "P0"), ("bus_coupler0", "P1")), (("ring_coupler1", "P0"), ("ring_coupler0", "P1")), (("bus_coupler1", "P3"), ("ring_coupler1", "P1")), ], "ports": [ ("bus_coupler0", "P0", "In"), ("bus_coupler1", "P2", "Drop"), ("bus_coupler0", "P2", "Through"), ("bus_coupler1", "P0", "Add"), ], "models": [pf.CircuitModel()],}
coupled_rings = pf.component_from_netlist(netlist_coupled_rings)coupled_ringss_matrix_coupled_rings = coupled_rings.s_matrix(frequencies=pf.C_0 / wavelengths)_ = pf.plot_s_matrix(s_matrix_coupled_rings, input_ports=["In"])
We can see how it is possible to change the filter response not just at the pass band but also modify it to achieve a sharp roll-off in the rejection band.
plt.plot( wavelengths, 20 * np.log10(np.abs(s_matrix_add_drop[(f"In@0", f"Drop@0")])), label=f"Single ring",)plt.plot( wavelengths, 20 * np.log10(np.abs(s_matrix_coupled_rings[(f"In@0", f"Drop@0")])), label=f"Coupled rings",)plt.xlabel("Wavelength (um)")plt.ylabel("Transmission (dB)")_ = plt.legend()
Cascaded add-drop filters
Section titled “Cascaded add-drop filters”Another common configuration is to cascade the rings in series in the same bus waveguide, possibly with slightly different resonant frequencies. We will use a termination on one of the add/drop filter ports and connect a routing waveguide bend to the other, which can be used to connect to other parts of the circuit later.
We start by creating the termination (from scratch) and the bend (using a parametric component):
termination = pf.Component("Termination")termination.add("Si", pf.stencil.linear_taper(20, (0.5, 0.1)))termination.add_port(pf.Port((0, 0), 0, port_spec))termination.add_model(pf.Tidy3DModel())terminationbend = pf.parametric.bend(name="Bend")bendNow we create the terminated single add/drop filter as a parametric component, using an extra straight section in the ring to change the resonant frequency. Because of the simplicity of this component, we can still use a netlist-driven design.
@pf.parametric_componentdef create_add_drop(*, added_length=0): # Basic add-drop filter netlist netlist = { "instances": { "bus_coupler0": bus_to_ring, "bus_coupler1": bus_to_ring, "bend0": bend, "bend1": bend, "termination": termination, }, "connections": [ (("bus_coupler1", "P1"), ("bus_coupler0", "P3")), (("bend1", "P0"), ("bus_coupler1", "P0")), (("termination", "P0"), ("bend1", "P1")), (("bend0", "P1"), ("bus_coupler1", "P2")), ], "ports": [ ("bus_coupler0", "P0"), ("bus_coupler0", "P2"), ("bend0", "P0", "Add/Drop"), ], "models": [pf.CircuitModel()], }
if added_length > 0: # If we need extra length, add 2 spacer instances to the netlist and # re-connect the instances appropriately spacer = pf.parametric.straight(length=added_length) netlist["instances"]["spacer0"] = spacer netlist["instances"]["spacer1"] = spacer
netlist["connections"] = [ (("spacer0", "P0"), ("bus_coupler0", "P1")), (("spacer1", "P1"), ("bus_coupler0", "P3")), (("bus_coupler1", "P1"), ("spacer1", "P0")), (("bend1", "P0"), ("bus_coupler1", "P0")), (("termination", "P0"), ("bend1", "P1")), (("bend0", "P1"), ("bus_coupler1", "P2")), ] return pf.component_from_netlist(netlist)
create_add_drop(added_length=5)We connect 3 terminated rings with an appropriate spacer between them. Each ring has a different resonance.
spacer = pf.parametric.straight(name="spacer", length=35)
netlist_cascaded_rings = { "name": "filter_circuit", "instances": { "AD0": create_add_drop(added_length=0.005), "AD1": create_add_drop(added_length=0.015), "AD2": create_add_drop(added_length=0.025), "SP1": spacer, "SP2": spacer, }, "connections": [ (("SP1", "P0"), ("AD0", "P1")), (("AD1", "P0"), ("SP1", "P1")), (("SP2", "P0"), ("AD1", "P1")), (("AD2", "P0"), ("SP2", "P1")), ], "ports": [ ("AD0", "P0"), ("AD2", "P1"), ("AD0", "Add/Drop", "AD0"), ("AD1", "Add/Drop", "AD1"), ("AD2", "Add/Drop", "AD2"), ], "models": [pf.CircuitModel()],}
cascaded_rings = pf.component_from_netlist(netlist_cascaded_rings)cascaded_ringsThe resonance shifts can be clearly seen in the transmission curve of the complete circuit:
_ = pf.plot_s_matrix(cascaded_rings.s_matrix(pf.C_0 / wavelengths), input_ports=["P0"])Uploading task 'Mode-StripTE1550nmw500nm…'14:29:52 -03 WARNING: Structure at 'structures[2]' 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: Structure at 'structures[2]' 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.
14:29:53 -03 WARNING: Structure at 'structures[2]' 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: Structure at 'structures[2]' 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.
Uploading task 'P0@0…'WARNING: Structure at 'structures[2]' 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.
Starting task 'Mode-StripTE1550nmw500nm': https://tidy3d.simulation.cloud/workbench?taskId=mo-abc56dd0-369c-4e77-ab3b-8309f71241e7Starting task 'P0@0': https://tidy3d.simulation.cloud/workbench?taskId=fdve-317815df-d6c2-46be-9e52-c38afb1626b5Downloading data from 'Mode-StripTE1550nmw500nm'…Downloading data from 'P0@0'…14:30:06 -03 WARNING: Structure at 'structures[2]' 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: Warning messages were found in the solver log. For more information, check 'SimulationData.log' or use 'web.download_log(task_id)'.

The warnings can be safely ignored in this case, because our waveguides don’t really end at the boundaries, but continue through the absorbing layers through adjacent structures.
Complete Design
Section titled “Complete Design”Finally, we will include grating couplers to the cascaded filter that can be used to couple light into and out of the chip. The SiEPIC technology already provides a grating designed for the port specification we chose, so we don’t have to design it from scratch:
grating_coupler = siepic.component("ebeam_gc_te1550")grating_couplerThe grating can be easily simulated in Tidy3D just as we did with other components, but because of its size, we will demonstrate the flexibility in PhotonForge to use pre-computed or experimental data from Touchstone files using a data model.
In order to facilitate the distribution of this example, the file data is embedded here, so we will first write it to a file (this data file would have been provided by previous computations or experiments).
grating_data = """[Version] 2.0# Hz S RI[Number of Ports] 2[Two-Port Data Order] 12_21[Number of Frequencies] 31[Network Data]1.897421e+14 -0.0352072 0.0326903 -0.226435 -0.447915 0.210738 0.421143 0.00676895 0.02649311.899825e+14 -0.0396145 0.00837585 -0.0362153 -0.512323 0.0336372 0.484589 0.00360995 0.02450641.902236e+14 -0.0354181 -0.010244 0.169495 -0.496585 -0.161001 0.47345 0.00310874 0.02140941.904653e+14 -0.026563 -0.0276462 0.357034 -0.398698 -0.341562 0.383859 0.00534486 0.01961471.907077e+14 -0.00635631 -0.0433821 0.493656 -0.230711 -0.47628 0.225361 0.00848205 0.02089791.909506e+14 0.0271043 -0.0439097 0.553475 -0.0179504 -0.538816 0.0205268 0.00983997 0.02506361.911942e+14 0.0573636 -0.016617 0.523126 0.20503 -0.513759 -0.198027 0.00772578 0.02989571.914384e+14 0.0591343 0.0296758 0.404572 0.400752 -0.400509 -0.392852 0.00269386 0.03258171.916832e+14 0.0241182 0.0647929 0.214641 0.535193 -0.214451 -0.52857 -0.00274075 0.03164721.919286e+14 -0.0260546 0.0625573 -0.0171573 0.582968 0.0150289 -0.578581 -0.0057533 0.02801151.921747e+14 -0.0569269 0.0256378 -0.252092 0.532182 0.249155 -0.53042 -0.00514607 0.02447941.924213e+14 -0.0517625 -0.0175119 -0.448512 0.38844 0.44637 -0.388954 -0.00231797 0.02386351.926687e+14 -0.0230183 -0.04036 -0.570368 0.175072 0.570088 -0.17651 -0.000417023 0.02691981.929166e+14 0.00586192 -0.0394758 -0.594637 -0.0712344 0.596004 0.0705136 -0.00209928 0.0316561.931652e+14 0.0243362 -0.0276428 -0.515563 -0.307241 0.517319 0.308136 -0.00752106 0.0347041.934145e+14 0.0364941 -0.0125098 -0.345937 -0.490763 0.34663 0.492995 -0.0141874 0.03378461.936644e+14 0.0435652 0.00978416 -0.115382 -0.588478 0.114132 0.59075 -0.0187811 0.02935221.939149e+14 0.0348038 0.0393164 0.135057 -0.582551 -0.137855 0.583101 -0.0195603 0.02418911.941661e+14 0.0019319 0.0592741 0.360515 -0.473915 -0.363013 0.471707 -0.0175394 0.02133981.944179e+14 -0.041254 0.0478519 0.520442 -0.282017 -0.520386 0.27783 -0.01565 0.02191321.946704e+14 -0.0635397 0.00473916 0.585755 -0.0419279 -0.582075 0.0380054 -0.0165477 0.02433951.949236e+14 -0.0456419 -0.0414984 0.545159 0.201759 -0.538441 -0.203066 -0.0206354 0.02559481.951774e+14 -0.000953603 -0.0582347 0.407918 0.404146 -0.399799 -0.401147 -0.0258056 0.02353071.954319e+14 0.0376615 -0.0388482 0.201359 0.529297 -0.194252 -0.521024 -0.0291419 0.01847681.956870e+14 0.0494522 -0.00407037 -0.0353287 0.556626 0.0382115 -0.54334 -0.0291566 0.01281211.959428e+14 0.0390625 0.0235674 -0.25862 0.483795 0.254042 -0.467837 -0.0267002 0.009033731.961993e+14 0.020525 0.0388267 -0.428387 0.326759 0.414603 -0.312517 -0.0240809 0.008014821.964564e+14 -0.00202446 0.0458016 -0.515291 0.116663 0.493245 -0.10946 -0.0232984 0.008593771.967142e+14 -0.0303964 0.0403233 -0.506173 -0.106335 0.479893 0.101871 -0.0246625 0.008610821.969727e+14 -0.0543518 0.0125664 -0.406116 -0.300777 0.382185 0.282568 -0.0267262 0.00658541.972319e+14 -0.0515758 -0.031471 -0.237294 -0.431789 0.223014 0.401693 -0.0275304 0.00276416[End]"""
_ = pathlib.Path("grating_data.s2p").write_text(grating_data)A file named grating_data.s2p should have been created in this notebook’s working folder. Now we can load it to use with the grating:
s_array, frequencies = pf.load_snp("grating_data.s2p")data_model = pf.DataModel( s_array=s_array, frequencies=frequencies, interpolation_method="akima")
_ = grating_coupler.add_model(data_model, "DataModel")Let’s plot the grating coupler’s S parameters in a denser and wider wavelength grid:
wavelengths_dense = np.linspace(1.525, 1.575, 1001)
_ = pf.plot_s_matrix( grating_coupler.s_matrix(pf.C_0 / wavelengths_dense), y="dB", input_ports=["P0"])
Now we can add the gratings to our cascaded filter and, if we so desire, export a layout file for fabrication:
netlist_layout = { "name": "main", "instances": { "filter": cascaded_rings, "grating0": grating_coupler, "grating1": grating_coupler, "spacer0": spacer, "spacer1": spacer, }, "connections": [ (("spacer0", "P0"), ("grating0", "P0")), (("filter", "P0"), ("spacer0", "P1")), (("spacer1", "P0"), ("filter", "P1")), (("grating1", "P0"), ("spacer1", "P1")), ], "ports": [ ("grating0", "P1"), ("grating1", "P1"), ("filter", "AD0"), ("filter", "AD1"), ("filter", "AD2"), ], "models": [pf.CircuitModel()],}
layout = pf.component_from_netlist(netlist_layout)
layout.write_gds("cascaded_rings.gds")Once again, we can simulate our full device, but now with a denser and wider wavelength grid to verify the full transmission spectrum between grating couplers. Note that this simulation may take a little longer because we are calculating modes using a fine frequency sweep.
wavelengths_wide = np.linspace(1.525, 1.575, 501)s_layout = layout.s_matrix(frequencies=pf.C_0 / wavelengths_wide)Uploading task 'Mode-StripTE1550nmw500nm…'Uploading task 'Mode-StripTE1550nmw500nm…'Uploading task 'P1@0…'Uploading task 'X1@0…'Uploading task 'Mode-StripTE1550nmw500nm…'Uploading task 'Mode-StripTE1550nmw500nm…'Uploading task 'P3@0…'Uploading task 'X3@0…'Uploading task 'P0@0…'Uploading task 'P1@0…'Uploading task 'P2@0…'Starting task 'Mode-StripTE1550nmw500nm': https://tidy3d.simulation.cloud/workbench?taskId=mo-16966587-f682-4a2d-b95d-dc5b7f605a65Starting task 'Mode-StripTE1550nmw500nm': https://tidy3d.simulation.cloud/workbench?taskId=mo-a56b6f44-379e-4601-895a-016d6429351dStarting task 'Mode-StripTE1550nmw500nm': https://tidy3d.simulation.cloud/workbench?taskId=mo-12ff1bf5-b39e-4559-a06b-0fbae64288ebUploading task 'P3@0…'Uploading task 'Mode-StripTE1550nmw500nm…'Starting task 'P1@0': https://tidy3d.simulation.cloud/workbench?taskId=fdve-ccc5f306-c142-44a6-ae8e-3aa8949ee45dUploading task 'Mode-StripTE1550nmw500nm…'Uploading task 'Mode-StripTE1550nmw500nm…'Uploading task 'Mode-StripTE1550nmw500nm…'Starting task 'X3@0': https://tidy3d.simulation.cloud/workbench?taskId=fdve-4214174d-beea-4516-bd6a-5d0a32133cb3Starting task 'X1@0': https://tidy3d.simulation.cloud/workbench?taskId=fdve-47418522-aef7-4895-aea2-b21e8a79d9ffUploading task 'Mode-StripTE1550nmw500nm…'Starting task 'P3@0': https://tidy3d.simulation.cloud/workbench?taskId=fdve-dfac6de5-1797-4503-8da4-2fa15bbe32ed14:30:17 -03 WARNING: Structure at 'structures[2]' 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.
14:30:18 -03 WARNING: Structure at 'structures[2]' 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: Structure at 'structures[2]' 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: Structure at 'structures[2]' 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.
Uploading task 'P0@0…'WARNING: Structure at 'structures[2]' 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: Structure at 'structures[2]' 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.
Downloading data from 'Mode-StripTE1550nmw500nm'…Downloading data from 'Mode-StripTE1550nmw500nm'…Downloading data from 'Mode-StripTE1550nmw500nm'…Starting task 'Mode-StripTE1550nmw500nm': https://tidy3d.simulation.cloud/workbench?taskId=mo-5abb877e-fae8-44af-967c-0542d505a958Downloading data from 'P1@0'…Downloading data from 'X1@0'…Starting task 'Mode-StripTE1550nmw500nm': https://tidy3d.simulation.cloud/workbench?taskId=mo-e3d2ed4c-52cd-41cb-a4fd-ecc9e9588243Downloading data from 'P3@0'…Starting task 'Mode-StripTE1550nmw500nm': https://tidy3d.simulation.cloud/workbench?taskId=mo-c1c444b0-7bed-4964-aee6-bb943ef08a29Starting task 'P0@0': https://tidy3d.simulation.cloud/workbench?taskId=fdve-77840009-baa0-4b62-afc6-2505e87005ddStarting task 'P2@0': https://tidy3d.simulation.cloud/workbench?taskId=fdve-3d214152-6246-4fff-a365-d945dd3be7e5Downloading data from 'X3@0'…Downloading data from 'Mode-StripTE1550nmw500nm'…Starting task 'P1@0': https://tidy3d.simulation.cloud/workbench?taskId=fdve-03ef998d-28ee-4770-b3e0-22d611d11fcaStarting task 'P0@0': https://tidy3d.simulation.cloud/workbench?taskId=fdve-a7da6c18-4b0a-47e3-a24b-cf3d2cdbb98aStarting task 'Mode-StripTE1550nmw500nm': https://tidy3d.simulation.cloud/workbench?taskId=mo-0ae59665-7320-4630-8b1e-5ccda01dc3cbStarting task 'P3@0': https://tidy3d.simulation.cloud/workbench?taskId=fdve-aa6b2b24-8fab-40ea-99b4-543fa10af0ffDownloading data from 'Mode-StripTE1550nmw500nm'…Downloading data from 'P2@0'…Starting task 'Mode-StripTE1550nmw500nm': https://tidy3d.simulation.cloud/workbench?taskId=mo-e1f29eab-50ed-400b-b7dd-58961d36ca2eDownloading data from 'P0@0'…Downloading data from 'Mode-StripTE1550nmw500nm'…Downloading data from 'P0@0'…Downloading data from 'P1@0'…Downloading data from 'Mode-StripTE1550nmw500nm'…Starting task 'Mode-StripTE1550nmw500nm': https://tidy3d.simulation.cloud/workbench?taskId=mo-47c38342-ab53-48d6-8cb7-822ec34fd8b5Downloading data from 'P3@0'…Downloading data from 'Mode-StripTE1550nmw500nm'…14:30:28 -03 WARNING: Structure at 'structures[2]' 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: Warning messages were found in the solver log. For more information, check 'SimulationData.log' or use 'web.download_log(task_id)'.
Downloading data from 'Mode-StripTE1550nmw500nm'…_, ax = plt.subplots(1, 1, figsize=(9, 3))ax.plot(wavelengths_wide, 20 * np.log10(np.abs(s_layout["P0@0", "P1@0"])))_ = ax.set(xlabel="Wavelength (μm)", ylabel="Transmission (dB)", ylim=(-30, None))
We see that overall filter response has a spectral dependence due to the wavelength response of the grating.
Testing Variations
Section titled “Testing Variations”The Circuit model allows us to test the effect of parameter changes in technologies, components, and models in specific references through the updates keyword argument available in the Circuit model’s start method.
In this case we want to see the effect of changing the resonance frequency of each ring in the filter circuit by modifying the added lengths in each of them.
For that, we set a dictionary of updates that first matches each add/drop filter by name ("create_add_drop_…") at any depth of the dependency tree (the value None in the reference specification tuple indicates that whatever follows ca match at any depth).
Within each add/drop filter, we select references whose component name starts with “straight” using a regular expression (.* will match any suffix), and, finally, update the model of those components (which are Waveguide models) with a new length parameter.
updates = { (None, "create_add_drop_added_length_0_005", "straight.*"): {"model_updates": {"length": 0.0}}, (None, "create_add_drop_added_length_0_015", "straight.*"): {"model_updates": {"length": 0.02}}, (None, "create_add_drop_added_length_0_025", "straight.*"): {"model_updates": {"length": 0.06}},}updated_s_matrix = layout.s_matrix( pf.C_0 / wavelengths_wide, model_kwargs={"updates": updates})
_, ax = plt.subplots(1, 1, figsize=(9, 3))ax.plot(wavelengths_wide, 20 * np.log10(np.abs(updated_s_matrix["P0@0", "P1@0"])))_ = ax.set(xlabel="Wavelength (μm)", ylabel="Transmission (dB)", ylim=(-30, None))14:30:59 -03 WARNING: Structure at 'structures[2]' 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.

Because we created each add/drop filter as a parametric component with the added_length parameter, we could also obtain the same result by updating the component themselves:
updates = { (None, "create_add_drop_added_length_0_005"): {"component_updates": {"added_length": 0.0}}, (None, "create_add_drop_added_length_0_015"): {"component_updates": {"added_length": 0.02}}, (None, "create_add_drop_added_length_0_025"): {"component_updates": {"added_length": 0.06}},}updated_s_matrix = layout.s_matrix( pf.C_0 / wavelengths_wide, model_kwargs={"updates": updates})
_, ax = plt.subplots(1, 1, figsize=(9, 3))ax.plot(wavelengths_wide, 20 * np.log10(np.abs(updated_s_matrix["P0@0", "P1@0"])))_ = ax.set(xlabel="Wavelength (μm)", ylabel="Transmission (dB)", ylim=(-30, None))14:31:04 -03 WARNING: Structure at 'structures[2]' 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.
