Coverage for qpdk / samples / simulate_resonator.py: 100%
24 statements
« prev ^ index » next coverage.py v7.13.4, created at 2026-03-14 10:27 +0000
« prev ^ index » next coverage.py v7.13.4, created at 2026-03-14 10:27 +0000
1# ---
2# jupyter:
3# jupytext:
4# text_representation:
5# extension: .py
6# format_name: percent
7# format_version: '1.3'
8# jupytext_version: 1.17.3
9# ---
11# %% [markdown]
12# # Export a resonator for simulation
13#
15# %%
17import gdsfactory as gf
18import numpy as np
19from gdsfactory.export import to_stl
21from qpdk.cells.airbridge import cpw_with_airbridges
22from qpdk.cells.launcher import launcher
23from qpdk.cells.resonator import resonator_coupled
24from qpdk.config import PATH
25from qpdk.tech import LAYER, route_bundle_sbend_cpw
27# %% [markdown]
28# ## System geometry
29#
30# Create a simulation layout with a resonator coupled to two probeline launchers.
31#
32# The layout consists of:
33# - A coupled resonator (with open start and specified coupling length)
34# - Two launcher components (mirrored and positioned symmetrically)
35# - CPW routes with airbridges connecting launchers to resonator coupling ports
36# - A simulation area layer enlarged around the layout
37# - Ports added for both launchers with prefixes
40# %%
41@gf.cell
42def resonator_simulation(coupling_gap: float = 12.0) -> gf.Component:
43 """Create a resonator simulation layout with launchers and CPW routes."""
44 c = gf.Component()
46 res_ref = c << resonator_coupled(
47 open_start=True, coupling_straight_length=300, coupling_gap=coupling_gap
48 )
49 res_ref.movex(-res_ref.size_info.width / 4)
51 launcher_left = c << launcher()
52 launcher_right = c << launcher()
53 launcher_right.mirror()
55 width_offset = res_ref.size_info.width + 700
56 launcher_left.move((-width_offset, 0))
57 launcher_right.move((width_offset, 0))
59 route_bundle_sbend_cpw(
60 c,
61 [launcher_left["o1"], launcher_right["o1"]],
62 [res_ref["coupling_o1"], res_ref["coupling_o2"]],
63 cross_section=cpw_with_airbridges(
64 airbridge_spacing=250.0, airbridge_padding=20.0
65 ),
66 )
68 c.kdb_cell.shapes(LAYER.SIM_AREA).insert(c.bbox().enlarged(0, 100))
70 c.add_ports(launcher_left.ports, prefix="left_")
71 c.add_ports(launcher_right.ports, prefix="right_")
73 return c
76if __name__ == "__main__":
77 from qpdk import PDK
79 PDK.activate()
81 # Create and display the filled version
82 c = gf.Component("resonator_simulation")
83 res_ref = c << resonator_simulation()
84 c.add_ports(res_ref.ports)
85 c.plot(
86 pixel_buffer_options=dict(width=1300, height=1000, oversampling=2, linewidth=3)
87 )
88 c.show()
90 # Export 3D model
91 to_stl(
92 c,
93 PATH.simulation / "resonator_simulations.stl",
94 layer_stack=PDK.layer_stack,
95 hull_invalid_polygons=True,
96 )
98 material_spec = {
99 "Si": {"relative_permittivity": 11.45},
100 "Nb": {"relative_permittivity": np.inf},
101 "vacuum": {"relative_permittivity": 1},
102 }
104 # TODO implement running simulations here
106 # from gplugins.palace import run_scattering_simulation_palace
107 #
108 # results = run_scattering_simulation_palace(
109 # c,
110 # layer_stack=PDK.layer_stack,
111 # material_spec=material_spec,
112 # only_one_port=True,
113 # driven_settings={
114 # "MinFreq": 0.1,
115 # "MaxFreq": 5,
116 # "FreqStep": 5,
117 # },
118 # n_processes=1,
119 # simulation_folder=Path().cwd() / "temporary",
120 # mesh_parameters=dict(
121 # background_tag="vacuum",
122 # background_padding=(0,) * 5 + (700,),
123 # port_names=[port.name for port in c.ports],
124 # default_characteristic_length=200,
125 # resolutions={
126 # "M1": {
127 # "resolution": 15,
128 # },
129 # "Silicon": {
130 # "resolution": 40,
131 # },
132 # "vacuum": {
133 # "resolution": 40,
134 # },
135 # **{
136 # f"M1__{port}": { # `__` is used as the layer to port delimiter for Elmer
137 # "resolution": 20,
138 # "DistMax": 30,
139 # "DistMin": 10,
140 # "SizeMax": 14,
141 # "SizeMin": 3,
142 # }
143 # for port in c.ports
144 # },
145 # },
146 # ),
147 # )
148 #
149 # display(results)
151 #
152 # df = results.scattering_matrix
153 # df.columns = df.columns.str.strip()
154 # s_complex = 10 ** (df["|S[2][1]| (dB)"].values / 20) * np.exp(
155 # 1j * np.deg2rad(df["arg(S[2][1]) (deg.)"].values)
156 # )
157 # # Assuming a 1-port reflection measurement for capacitance extraction
158 # y11 = (1 - s_complex) / (1 + s_complex) / 50.0
159 # freq_hz = df["f (GHz)"].values * 1e9
160 # cap = np.imag(y11) / (freq_hz * 2 * np.pi)
161 # display(cap)
162 #
163 # plt.plot(freq_hz / 1e9, cap * 1e15)
164 # plt.xlabel("Freq (GHz)")
165 # plt.ylabel("C (fF)")
166 #
167 # if results.field_file_locations:
168 # pv.start_xvfb()
169 # pv.set_jupyter_backend("trame")
170 # field = pv.read(results.field_file_locations[0])
171 # slice = field.slice_orthogonal(z=layer_stack.layers["bw"].zmin * 1e-6)
172 #
173 # p = pv.Plotter()
174 # p.add_mesh(slice, scalars="Ue", cmap="turbo")
175 # p.show_grid()
176 # p.camera_position = "xy"
177 # p.enable_parallel_projection()
178 # p.show()