Coverage for qpdk / samples / simulate_resonator.py: 100%
24 statements
« prev ^ index » next coverage.py v7.13.5, created at 2026-04-02 17:50 +0000
« prev ^ index » next coverage.py v7.13.5, created at 2026-04-02 17:50 +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.
45 Returns:
46 The resonator simulation component.
47 """
48 c = gf.Component()
50 res_ref = c << resonator_coupled(
51 open_start=True, coupling_straight_length=300, coupling_gap=coupling_gap
52 )
53 res_ref.movex(-res_ref.size_info.width / 4)
55 launcher_left = c << launcher()
56 launcher_right = c << launcher()
57 launcher_right.mirror()
59 width_offset = res_ref.size_info.width + 700
60 launcher_left.move((-width_offset, 0))
61 launcher_right.move((width_offset, 0))
63 route_bundle_sbend_cpw(
64 c,
65 [launcher_left["o1"], launcher_right["o1"]],
66 [res_ref["coupling_o1"], res_ref["coupling_o2"]],
67 cross_section=cpw_with_airbridges(
68 airbridge_spacing=250.0, airbridge_padding=20.0
69 ),
70 )
72 c.kdb_cell.shapes(LAYER.SIM_AREA).insert(c.bbox().enlarged(0, 100))
74 c.add_ports(launcher_left.ports, prefix="left_")
75 c.add_ports(launcher_right.ports, prefix="right_")
77 return c
80if __name__ == "__main__":
81 from qpdk import PDK
83 PDK.activate()
85 # Create and display the filled version
86 c = gf.Component("resonator_simulation")
87 res_ref = c << resonator_simulation()
88 c.add_ports(res_ref.ports)
89 c.plot(
90 pixel_buffer_options=dict(width=1300, height=1000, oversampling=2, linewidth=3)
91 )
92 c.show()
94 # Export 3D model
95 to_stl(
96 c,
97 PATH.simulation / "resonator_simulations.stl",
98 layer_stack=PDK.layer_stack,
99 hull_invalid_polygons=True,
100 )
102 material_spec = {
103 "Si": {"relative_permittivity": 11.45},
104 "Nb": {"relative_permittivity": np.inf},
105 "vacuum": {"relative_permittivity": 1},
106 }
108 # TODO implement running simulations here
110 # from gplugins.palace import run_scattering_simulation_palace
111 #
112 # results = run_scattering_simulation_palace(
113 # c,
114 # layer_stack=PDK.layer_stack,
115 # material_spec=material_spec,
116 # only_one_port=True,
117 # driven_settings={
118 # "MinFreq": 0.1,
119 # "MaxFreq": 5,
120 # "FreqStep": 5,
121 # },
122 # n_processes=1,
123 # simulation_folder=Path().cwd() / "temporary",
124 # mesh_parameters=dict(
125 # background_tag="vacuum",
126 # background_padding=(0,) * 5 + (700,),
127 # port_names=[port.name for port in c.ports],
128 # default_characteristic_length=200,
129 # resolutions={
130 # "M1": {
131 # "resolution": 15,
132 # },
133 # "Silicon": {
134 # "resolution": 40,
135 # },
136 # "vacuum": {
137 # "resolution": 40,
138 # },
139 # **{
140 # f"M1__{port}": { # `__` is used as the layer to port delimiter for Elmer
141 # "resolution": 20,
142 # "DistMax": 30,
143 # "DistMin": 10,
144 # "SizeMax": 14,
145 # "SizeMin": 3,
146 # }
147 # for port in c.ports
148 # },
149 # },
150 # ),
151 # )
152 #
153 # display(results)
155 #
156 # df = results.scattering_matrix
157 # df.columns = df.columns.str.strip()
158 # s_complex = 10 ** (df["|S[2][1]| (dB)"].values / 20) * np.exp(
159 # 1j * np.deg2rad(df["arg(S[2][1]) (deg.)"].values)
160 # )
161 # # Assuming a 1-port reflection measurement for capacitance extraction
162 # y11 = (1 - s_complex) / (1 + s_complex) / 50.0
163 # freq_hz = df["f (GHz)"].values * 1e9
164 # cap = np.imag(y11) / (freq_hz * 2 * np.pi)
165 # display(cap)
166 #
167 # plt.plot(freq_hz / 1e9, cap * 1e15)
168 # plt.xlabel("Freq (GHz)")
169 # plt.ylabel("C (fF)")
170 #
171 # if results.field_file_locations:
172 # pv.start_xvfb()
173 # pv.set_jupyter_backend("trame")
174 # field = pv.read(results.field_file_locations[0])
175 # slice = field.slice_orthogonal(z=layer_stack.layers["bw"].zmin * 1e-6)
176 #
177 # p = pv.Plotter()
178 # p.add_mesh(slice, scalars="Ue", cmap="turbo")
179 # p.show_grid()
180 # p.camera_position = "xy"
181 # p.enable_parallel_projection()
182 # p.show()