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.
[1]:
import pathlib
import matplotlib.pyplot as plt
import numpy as np
import photonforge as pf
import siepic_forge as siepic
import tidy3d as td
07:24:53 -03 WARNING: The material-library variant 'Palik_Lossless' is deprecated and maps to 'Palik_LowLoss' because it contains a tiny fitted loss despite its name. Use 'Palik_NoLoss' where available for a zero-loss Palik model.
For the design, we will will use the SiEPIC OpenEBL technology and the its predefined strip waveguide for TE mode in the C band.
[2]:
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_1550
[3]:
wavelengths = np.linspace(1.548, 1.552, 81)
port_spec = tech.ports["TE_1550_500"]
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).
[4]:
pf.config.default_mesh_refinement = 15
# Default parameters for parametric components
pf.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,
},
}
[5]:
bus_to_ring = pf.parametric.s_bend_ring_coupler(s_bend_offset=port_spec.width)
bus_to_ring
[5]:
[6]:
ring_to_ring = pf.parametric.dual_ring_coupler()
ring_to_ring
[6]:
We will run the S parameter simulation and plot it with our S matrix plot utility for both couplers.
[7]:
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…'
Uploading task 'P0@0…'
Uploading task 'P1@0…'
Uploading task 'P2@0…'
Uploading task 'P3@0…'
Starting task 'Mode-StripTE1550nmw500nm': https://tidy3d.simulation.cloud/workbench?taskId=mo-00c13aa3-0207-46a9-a1a8-970b7b24bc19
Starting task 'P1@0': https://tidy3d.simulation.cloud/workbench?taskId=fdve-5be95ec9-4c18-46d5-8b81-fd013be7079b
Starting task 'Mode-StripTE1550nmw500nm': https://tidy3d.simulation.cloud/workbench?taskId=mo-d0c40298-e935-4bed-a349-401f4b4f52dd
Downloading data from 'Mode-StripTE1550nmw500nm'…
Downloading data from 'P1@0'…
Starting task 'Mode-StripTE1550nmw500nm': https://tidy3d.simulation.cloud/workbench?taskId=mo-fcde7f93-0cbc-475b-a91e-1e2b1a266bc4
Starting task 'X1@0': https://tidy3d.simulation.cloud/workbench?taskId=fdve-4e618fae-9b43-44af-ad65-d7f91e209dfb
Downloading data from 'Mode-StripTE1550nmw500nm'…
Progress: 9% /
07:25:11 -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 'Mode-StripTE1550nmw500nm'…
Downloading data from 'X1@0'…
Starting task 'Mode-StripTE1550nmw500nm': https://tidy3d.simulation.cloud/workbench?taskId=mo-6fd46639-25f5-4001-a81b-1ae172ae1682
Starting task 'P3@0': https://tidy3d.simulation.cloud/workbench?taskId=fdve-b273bd18-e75f-4530-8aa5-30d133115701
Starting task 'P2@0': https://tidy3d.simulation.cloud/workbench?taskId=fdve-8a83e25d-1d46-45ae-a6ee-ca1c8680ea37
Starting task 'X3@0': https://tidy3d.simulation.cloud/workbench?taskId=fdve-a6decf29-0f40-4e18-b73d-48fbafb67e1e
Starting task 'P1@0': https://tidy3d.simulation.cloud/workbench?taskId=fdve-193d7f27-ed1e-421a-bd3e-70ab95bc0f7d
Starting task 'P3@0': https://tidy3d.simulation.cloud/workbench?taskId=fdve-65f4ecaa-335c-4b0e-9c06-79f020157559
Downloading data from 'Mode-StripTE1550nmw500nm'…
Starting task 'P0@0': https://tidy3d.simulation.cloud/workbench?taskId=fdve-e3ad562f-acb2-4aef-a409-d0839a3d2474
Downloading data from 'P3@0'…
Downloading data from 'X3@0'…
Downloading data from 'P1@0'…
Downloading data from 'P2@0'…
Downloading data from 'P3@0'…
Progress: 22% \
07:25:17 -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 'P0@0'…
Progress: 100%
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…'
Uploading task 'P3@0…'
Starting task 'P0@0': https://tidy3d.simulation.cloud/workbench?taskId=fdve-1d6cb8c8-7df7-4c37-84b0-0fa46430b5cf
Downloading data from 'P0@0'…
Starting task 'X0@0': https://tidy3d.simulation.cloud/workbench?taskId=fdve-0293cf0a-6934-4322-b7ac-0d98911e705b
Starting task 'P1@0': https://tidy3d.simulation.cloud/workbench?taskId=fdve-cc820d8a-158b-43bd-bed9-c4b52a8d2f71
Starting task 'X1@0': https://tidy3d.simulation.cloud/workbench?taskId=fdve-ddc86d45-36fd-4aad-ac11-1dc17cc29c47
Starting task 'X2@0': https://tidy3d.simulation.cloud/workbench?taskId=fdve-1d1baa83-2479-429b-bee4-3c659cd4e84f
Starting task 'P2@0': https://tidy3d.simulation.cloud/workbench?taskId=fdve-1aae60c4-f0af-466c-94a8-6b6c436acf6a
Downloading data from 'X0@0'…
Starting task 'P3@0': https://tidy3d.simulation.cloud/workbench?taskId=fdve-bd151a7a-c7b2-4b64-b0a3-de86e79d8497
Downloading data from 'P1@0'…
Downloading data from 'X1@0'…
Downloading data from 'X2@0'…
Downloading data from 'P2@0'…
Progress: 11% /
07:25:38 -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'…
Starting task 'X3@0': https://tidy3d.simulation.cloud/workbench?taskId=fdve-0ff1c22b-5572-4d48-9b00-d27afa4c7e99
Progress: 19% \
07:25:40 -03 WARNING: Warning messages were found in the solver log. For more information, check 'SimulationData.log' or use 'web.download_log(task_id)'.
Starting task 'P1@0': https://tidy3d.simulation.cloud/workbench?taskId=fdve-b7e8bcf4-50a9-40ab-8d53-41f3ebb1ab92
Starting task 'P2@0': https://tidy3d.simulation.cloud/workbench?taskId=fdve-fe3ef21d-6777-4e76-b415-7355de030c42
Downloading data from 'X3@0'…
Downloading data from 'P1@0'…
Starting task 'P0@0': https://tidy3d.simulation.cloud/workbench?taskId=fdve-a636e5a8-a7b7-4fed-acec-80454f5d3149
Downloading data from 'P2@0'…
Starting task 'P3@0': https://tidy3d.simulation.cloud/workbench?taskId=fdve-05a86a84-c074-4b6a-85b0-50f921639500
Downloading data from 'P0@0'…
Downloading data from 'P3@0'…
Progress: 100%
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.
[8]:
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_drop
[8]:
Since 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.
[9]:
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-a7c26e0c-e54d-46df-9102-72be0fb63be6
Downloading data from 'Mode-StripTE1550nmw500nm'…
Starting task 'Mode-StripTE1550nmw500nm': https://tidy3d.simulation.cloud/workbench?taskId=mo-18fcb533-ff6d-4fb2-90ff-63bb59b8ac76
Starting task 'Mode-StripTE1550nmw500nm': https://tidy3d.simulation.cloud/workbench?taskId=mo-6c367e43-dc34-4d93-be41-dc064dfa750b
Starting task 'Mode-StripTE1550nmw500nm': https://tidy3d.simulation.cloud/workbench?taskId=mo-9016e31e-126e-47d6-83ca-520f74422933
Downloading data from 'Mode-StripTE1550nmw500nm'…
Downloading data from 'Mode-StripTE1550nmw500nm'…
Downloading data from 'Mode-StripTE1550nmw500nm'…
Progress: 100%
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.
[10]:
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_rings
[10]:
[11]:
s_matrix_coupled_rings = coupled_rings.s_matrix(frequencies=pf.C_0 / wavelengths)
_ = pf.plot_s_matrix(s_matrix_coupled_rings, input_ports=["In"])
Progress: 100%
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.
[12]:
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¶
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):
[13]:
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())
termination
[13]:
[14]:
bend = pf.parametric.bend(name="Bend")
bend
[14]:
Now 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.
[15]:
@pf.parametric_component
def 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)
[15]:
We connect 3 terminated rings with an appropriate spacer between them. Each ring has a different resonance.
[16]:
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_rings
[16]:
The resonance shifts can be clearly seen in the transmission curve of the complete circuit:
[17]:
_ = pf.plot_s_matrix(cascaded_rings.s_matrix(pf.C_0 / wavelengths), input_ports=["P0"])
Uploading task 'Mode-StripTE1550nmw500nm…'
07:26:25 -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.
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.
Starting task 'Mode-StripTE1550nmw500nm': https://tidy3d.simulation.cloud/workbench?taskId=mo-27871ab3-ed14-4849-9d81-6b57877938fd
Downloading data from 'Mode-StripTE1550nmw500nm'…
Starting task 'P0@0': https://tidy3d.simulation.cloud/workbench?taskId=fdve-2cb5481b-11fa-4cad-83b9-01fb65da1282
Downloading data from 'P0@0'…
Progress: 67% \
07:26:44 -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)'.
Progress: 100%
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¶
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:
[18]:
grating_coupler = siepic.component("ebeam_gc_te1550")
grating_coupler
[18]:
The 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).
[19]:
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.0264931
1.899825e+14 -0.0396145 0.00837585 -0.0362153 -0.512323 0.0336372 0.484589 0.00360995 0.0245064
1.902236e+14 -0.0354181 -0.010244 0.169495 -0.496585 -0.161001 0.47345 0.00310874 0.0214094
1.904653e+14 -0.026563 -0.0276462 0.357034 -0.398698 -0.341562 0.383859 0.00534486 0.0196147
1.907077e+14 -0.00635631 -0.0433821 0.493656 -0.230711 -0.47628 0.225361 0.00848205 0.0208979
1.909506e+14 0.0271043 -0.0439097 0.553475 -0.0179504 -0.538816 0.0205268 0.00983997 0.0250636
1.911942e+14 0.0573636 -0.016617 0.523126 0.20503 -0.513759 -0.198027 0.00772578 0.0298957
1.914384e+14 0.0591343 0.0296758 0.404572 0.400752 -0.400509 -0.392852 0.00269386 0.0325817
1.916832e+14 0.0241182 0.0647929 0.214641 0.535193 -0.214451 -0.52857 -0.00274075 0.0316472
1.919286e+14 -0.0260546 0.0625573 -0.0171573 0.582968 0.0150289 -0.578581 -0.0057533 0.0280115
1.921747e+14 -0.0569269 0.0256378 -0.252092 0.532182 0.249155 -0.53042 -0.00514607 0.0244794
1.924213e+14 -0.0517625 -0.0175119 -0.448512 0.38844 0.44637 -0.388954 -0.00231797 0.0238635
1.926687e+14 -0.0230183 -0.04036 -0.570368 0.175072 0.570088 -0.17651 -0.000417023 0.0269198
1.929166e+14 0.00586192 -0.0394758 -0.594637 -0.0712344 0.596004 0.0705136 -0.00209928 0.031656
1.931652e+14 0.0243362 -0.0276428 -0.515563 -0.307241 0.517319 0.308136 -0.00752106 0.034704
1.934145e+14 0.0364941 -0.0125098 -0.345937 -0.490763 0.34663 0.492995 -0.0141874 0.0337846
1.936644e+14 0.0435652 0.00978416 -0.115382 -0.588478 0.114132 0.59075 -0.0187811 0.0293522
1.939149e+14 0.0348038 0.0393164 0.135057 -0.582551 -0.137855 0.583101 -0.0195603 0.0241891
1.941661e+14 0.0019319 0.0592741 0.360515 -0.473915 -0.363013 0.471707 -0.0175394 0.0213398
1.944179e+14 -0.041254 0.0478519 0.520442 -0.282017 -0.520386 0.27783 -0.01565 0.0219132
1.946704e+14 -0.0635397 0.00473916 0.585755 -0.0419279 -0.582075 0.0380054 -0.0165477 0.0243395
1.949236e+14 -0.0456419 -0.0414984 0.545159 0.201759 -0.538441 -0.203066 -0.0206354 0.0255948
1.951774e+14 -0.000953603 -0.0582347 0.407918 0.404146 -0.399799 -0.401147 -0.0258056 0.0235307
1.954319e+14 0.0376615 -0.0388482 0.201359 0.529297 -0.194252 -0.521024 -0.0291419 0.0184768
1.956870e+14 0.0494522 -0.00407037 -0.0353287 0.556626 0.0382115 -0.54334 -0.0291566 0.0128121
1.959428e+14 0.0390625 0.0235674 -0.25862 0.483795 0.254042 -0.467837 -0.0267002 0.00903373
1.961993e+14 0.020525 0.0388267 -0.428387 0.326759 0.414603 -0.312517 -0.0240809 0.00801482
1.964564e+14 -0.00202446 0.0458016 -0.515291 0.116663 0.493245 -0.10946 -0.0232984 0.00859377
1.967142e+14 -0.0303964 0.0403233 -0.506173 -0.106335 0.479893 0.101871 -0.0246625 0.00861082
1.969727e+14 -0.0543518 0.0125664 -0.406116 -0.300777 0.382185 0.282568 -0.0267262 0.0065854
1.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:
[20]:
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:
[21]:
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"]
)
Progress: 100%
Now we can add the gratings to our cascaded filter and, if we so desire, export a layout file for fabrication:
[22]:
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")
[22]:
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.
[23]:
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…'
Uploading task 'P3@0…'
Uploading task 'Mode-StripTE1550nmw500nm…'
Uploading task 'Mode-StripTE1550nmw500nm…'
Uploading task 'Mode-StripTE1550nmw500nm…'
Uploading task 'Mode-StripTE1550nmw500nm…'
Uploading task 'Mode-StripTE1550nmw500nm…'
07:26:50 -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.
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…'
Starting task 'Mode-StripTE1550nmw500nm': https://tidy3d.simulation.cloud/workbench?taskId=mo-34e3dc63-6ad1-4658-81f6-dfdbd9ba9b34
Starting task 'Mode-StripTE1550nmw500nm': https://tidy3d.simulation.cloud/workbench?taskId=mo-f7470fe4-54ae-449c-a996-360b3f39a30a
Downloading data from 'Mode-StripTE1550nmw500nm'…
Downloading data from 'Mode-StripTE1550nmw500nm'…
Starting task 'Mode-StripTE1550nmw500nm': https://tidy3d.simulation.cloud/workbench?taskId=mo-0753c686-377a-43ce-a019-e14e4d0bdce1
Starting task 'P3@0': https://tidy3d.simulation.cloud/workbench?taskId=fdve-e3ec92d3-48c4-4a87-be6f-9fe63d0936ce
Starting task 'Mode-StripTE1550nmw500nm': https://tidy3d.simulation.cloud/workbench?taskId=mo-02f86bce-0f6d-42df-b408-f4ccf3923852
Starting task 'X3@0': https://tidy3d.simulation.cloud/workbench?taskId=fdve-c1d7c389-b0f9-4c49-8b9e-c3c515959690
Starting task 'P2@0': https://tidy3d.simulation.cloud/workbench?taskId=fdve-94120c3a-df1d-4723-a8b6-ebb897b9a26b
Downloading data from 'Mode-StripTE1550nmw500nm'…
Starting task 'P1@0': https://tidy3d.simulation.cloud/workbench?taskId=fdve-f587d583-1f7c-4b1d-9a2d-e516934f5905
Downloading data from 'P3@0'…
Downloading data from 'Mode-StripTE1550nmw500nm'…
Downloading data from 'X3@0'…
Starting task 'X1@0': https://tidy3d.simulation.cloud/workbench?taskId=fdve-81b566a2-e65b-4eec-9c6e-0a627655b7bc
Starting task 'P0@0': https://tidy3d.simulation.cloud/workbench?taskId=fdve-568c3ddb-b545-40bd-a9ae-451be02196a2
Downloading data from 'P2@0'…
Downloading data from 'P1@0'…
Starting task 'Mode-StripTE1550nmw500nm': https://tidy3d.simulation.cloud/workbench?taskId=mo-6ed360f8-be9e-4a50-b1ed-abc3c049b66a
Starting task 'Mode-StripTE1550nmw500nm': https://tidy3d.simulation.cloud/workbench?taskId=mo-8857d41d-050b-4214-8f1f-6861670bc1cb
Downloading data from 'X1@0'…
Starting task 'P3@0': https://tidy3d.simulation.cloud/workbench?taskId=fdve-155885de-2f50-4ad5-9f46-42a4d8bbb50f
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-9e36261c-245f-4b5b-ae33-7068ccf57fd7
Starting task 'Mode-StripTE1550nmw500nm': https://tidy3d.simulation.cloud/workbench?taskId=mo-f47d8157-4ec7-45f1-be96-b7f16752a37d
Starting task 'Mode-StripTE1550nmw500nm': https://tidy3d.simulation.cloud/workbench?taskId=mo-e96ff7c5-c0e9-480b-b3f5-84bce71cca63
Downloading data from 'P3@0'…
Starting task 'Mode-StripTE1550nmw500nm': https://tidy3d.simulation.cloud/workbench?taskId=mo-cf3357e9-de6a-488f-b473-d3cf6f3a9f4b
Downloading data from 'Mode-StripTE1550nmw500nm'…
Downloading data from 'Mode-StripTE1550nmw500nm'…
Downloading data from 'P1@0'…
Downloading data from 'Mode-StripTE1550nmw500nm'…
Starting task 'P0@0': https://tidy3d.simulation.cloud/workbench?taskId=fdve-2628191c-3806-465f-b2e8-1eb77a51aa2c
Downloading data from 'P0@0'…
Progress: 5% \
07:27:19 -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)'.
Progress: 100%
[24]:
_, 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¶
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.
[25]:
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))
Starting…
07:27: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.
Progress: 100%
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:
[26]:
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))
Starting…
07:27:32 -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.
Progress: 100%