Source code for qpdk.samples.simulate_resonator

# ---
# jupyter:
#   jupytext:
#     text_representation:
#       extension: .py
#       format_name: percent
#       format_version: '1.3'
#       jupytext_version: 1.17.3
# ---

# %% [markdown]
# # Export a resonator for simulation
#

# %%

import gdsfactory as gf
import numpy as np
from gdsfactory.export import to_stl

from qpdk.cells.airbridge import cpw_with_airbridges
from qpdk.cells.launcher import launcher
from qpdk.cells.resonator import resonator_coupled
from qpdk.config import PATH
from qpdk.tech import LAYER, route_bundle_sbend_cpw

# %% [markdown]
# ## System geometry
#
# Create a simulation layout with a resonator coupled to two probeline launchers.
#
# The layout consists of:
#   - A coupled resonator (with open start and specified coupling length)
#   - Two launcher components (mirrored and positioned symmetrically)
#   - CPW routes with airbridges connecting launchers to resonator coupling ports
#   - A simulation area layer enlarged around the layout
#   - Ports added for both launchers with prefixes


# %%
[docs] @gf.cell def resonator_simulation(coupling_gap: float = 12.0) -> gf.Component: """Create a resonator simulation layout with launchers and CPW routes.""" c = gf.Component() res_ref = c << resonator_coupled( {"open_start": True}, coupling_straight_length=300, coupling_gap=coupling_gap ) res_ref.movex(-res_ref.size_info.width / 4) launcher_left = c << launcher() launcher_right = c << launcher() launcher_right.mirror() width_offset = res_ref.size_info.width + 700 launcher_left.move((-width_offset, 0)) launcher_right.move((width_offset, 0)) route_bundle_sbend_cpw( c, [launcher_left["o1"], launcher_right["o1"]], [res_ref["coupling_o1"], res_ref["coupling_o2"]], cross_section=cpw_with_airbridges( airbridge_spacing=250.0, airbridge_padding=20.0 ), ) c.kdb_cell.shapes(LAYER.SIM_AREA).insert(c.bbox().enlarged(0, 100)) c.add_ports(launcher_left.ports, prefix="left_") c.add_ports(launcher_right.ports, prefix="right_") return c
if __name__ == "__main__": from qpdk import PDK PDK.activate() # Create and display the filled version c = gf.Component("resonator_simulation") res_ref = c << resonator_simulation() c.add_ports(res_ref.ports) c.plot( pixel_buffer_options=dict(width=1300, height=1000, oversampling=2, linewidth=3) ) c.show() # Export 3D model to_stl( c, PATH.simulation / "resonator_simulations.stl", layer_stack=PDK.layer_stack, hull_invalid_polygons=True, ) material_spec = { "Si": {"relative_permittivity": 11.45}, "Nb": {"relative_permittivity": np.inf}, "vacuum": {"relative_permittivity": 1}, } # TODO implement running simulations here # from gplugins.palace import run_scattering_simulation_palace # # results = run_scattering_simulation_palace( # c, # layer_stack=PDK.layer_stack, # material_spec=material_spec, # only_one_port=True, # driven_settings={ # "MinFreq": 0.1, # "MaxFreq": 5, # "FreqStep": 5, # }, # n_processes=1, # simulation_folder=Path().cwd() / "temporary", # mesh_parameters=dict( # background_tag="vacuum", # background_padding=(0,) * 5 + (700,), # port_names=[port.name for port in c.ports], # default_characteristic_length=200, # resolutions={ # "M1": { # "resolution": 15, # }, # "Silicon": { # "resolution": 40, # }, # "vacuum": { # "resolution": 40, # }, # **{ # f"M1__{port}": { # `__` is used as the layer to port delimiter for Elmer # "resolution": 20, # "DistMax": 30, # "DistMin": 10, # "SizeMax": 14, # "SizeMin": 3, # } # for port in c.ports # }, # }, # ), # ) # # display(results) # import skrf # # df = results.scattering_matrix # df.columns = df.columns.str.strip() # s_complex = 10 ** df["|S[2][1]| (dB)"].values * np.exp( # 1j * skrf.degree_2_radian(df["arg(S[2][1]) (deg.)"].values) # ) # ntw = skrf.Network(f=df["f (GHz)"].values, s=s_complex, z0=50) # cap = np.imag(ntw.y.flatten()) / (ntw.f * 2 * np.pi) # display(cap) # # plt.plot(ntw.f, cap * 1e15) # plt.xlabel("Freq (GHz)") # plt.ylabel("C (fF)") # # if results.field_file_locations: # pv.start_xvfb() # pv.set_jupyter_backend("trame") # field = pv.read(results.field_file_locations[0]) # slice = field.slice_orthogonal(z=layer_stack.layers["bw"].zmin * 1e-6) # # p = pv.Plotter() # p.add_mesh(slice, scalars="Ue", cmap="turbo") # p.show_grid() # p.camera_position = "xy" # p.enable_parallel_projection() # p.show()