# ---
# jupyter:
# jupytext:
# text_representation:
# extension: .py
# format_name: percent
# format_version: '1.3'
# jupytext_version: 1.17.3
# ---
# %% [markdown]
# # Resonator Test Chip
#
# This example demonstrates creating a resonator test chip for characterizing superconducting microwave resonators.
#
# The design is inspired by Norris, G.J., Michaud, L., Pahl, D. et al. "Improved parameter targeting in 3D-integrated superconducting circuits through a polymer spacer process." EPJ Quantum Technol. 11, 5 (2024). https://doi.org/10.1140/epjqt/s40507-023-00213-x
# %%
import gdsfactory as gf
import numpy as np
from qpdk.cells.helpers import fill_magnetic_vortices
from qpdk.cells.launcher import launcher
from qpdk.cells.resonator import resonator_coupled
from qpdk.cells.waveguides import straight
from qpdk.tech import (
coplanar_waveguide,
route_single_cpw,
route_single_sbend,
)
# %% [markdown]
# ## Resonator Test Chip Function
#
# Creates a test chip with two probelines and multiple resonators for characterization.
# %%
[docs]
@gf.cell
def resonator_test_chip(
probeline_length: float = 9000.0,
probeline_separation: float = 1000.0,
resonator_length: float = 4000.0,
coupling_length: float = 200.0,
coupling_gap: float = 16.0,
) -> gf.Component:
"""Creates a resonator test chip with two probelines and 16 resonators.
The chip features two horizontal probelines running west to east, each with
launchers on both ends. Eight quarter-wave resonators are coupled to each
probeline, with systematically varied cross-section parameters for
characterization studies.
Args:
probeline_length: Length of each probeline in µm.
probeline_separation: Vertical separation between probelines in µm.
resonator_length: Length of each resonator in µm.
coupling_length: Length of coupling region between resonator and probeline in µm.
coupling_gap: Gap between resonator and probeline for coupling in µm.
Returns:
Component: A gdsfactory component containing the complete test chip layout.
"""
c = gf.Component()
# Create different cross-sections for resonators with systematic parameter variation
# 8 different combinations of width and gap for each probeline
width_values = np.linspace(8, 30, 8, dtype=int)
gap_values = np.linspace(6, 20, 8, dtype=int)
n_resonators = len(width_values)
resonator_cross_sections = [
coplanar_waveguide(width=width_values[i], gap=gap_values[i])
for i in range(n_resonators)
]
# Standard cross-section for probelines
probeline_xs = coplanar_waveguide(width=10, gap=6)
probeline_y_positions = [0, probeline_separation]
for probeline_idx, y_pos in enumerate(probeline_y_positions):
# Add launchers at both ends
launcher_west = c.add_ref(launcher())
launcher_west.move((0, y_pos))
launcher_east = c.add_ref(launcher()) # Create some probeline straight
launcher_east.mirror_x()
launcher_east.move((probeline_length, y_pos))
# Add resonators along the probeline
resonator_spacing = probeline_length / 9 # Space for 8 resonators
previous_port = launcher_west.ports["o1"]
for res_idx in range(n_resonators):
# Calculate resonator position along probeline
x_position = (res_idx + 1) * resonator_spacing
# Create resonator with unique cross-section
resonator_params = {
"length": resonator_length,
"meanders": 6,
"cross_section": resonator_cross_sections[res_idx],
"open_start": True,
"open_end": False, # Quarter-wave resonator
}
coupled_resonator = resonator_coupled(
resonator_params=resonator_params,
cross_section_non_resonator=probeline_xs,
coupling_straight_length=coupling_length,
coupling_gap=coupling_gap,
)
resonator_ref = c.add_ref(coupled_resonator)
# Position resonator above probeline
if probeline_idx != 0:
resonator_ref.mirror_y()
resonator_ref.move((x_position - resonator_ref.size_info.width / 2, y_pos))
gf.logger.debug(f"Added resonator {res_idx} at x={x_position} µm")
if res_idx == 0:
# Add some straight before connecting the first resonator
first_straight_ref = c.add_ref(
straight(length=200.0, cross_section=probeline_xs)
)
first_straight_ref.connect("o1", resonator_ref.ports["coupling_o1"])
route_single_sbend(
c,
port1=previous_port,
port2=first_straight_ref.ports["o2"],
cross_section=probeline_xs,
)
else:
route_single_cpw(
c,
port1=previous_port,
port2=resonator_ref.ports["coupling_o1"],
cross_section=probeline_xs,
)
previous_port = resonator_ref.ports["coupling_o2"]
# Add some straight before connecting to the final launcher
final_straight_ref = c.add_ref(
straight(length=400.0, cross_section=probeline_xs)
)
final_straight_ref.connect("o1", previous_port)
# Connect final launcher to probeline
route_single_sbend(
c,
port1=final_straight_ref.ports["o2"],
port2=launcher_east.ports["o1"],
cross_section=probeline_xs,
)
return c
# %% [markdown]
# ## Filled Resonator Test Chip
#
# Version of the test chip with magnetic vortex trapping holes in the ground plane.
# %%
[docs]
@gf.cell
def filled_resonator_test_chip() -> gf.Component:
"""Creates a resonator test chip filled with magnetic vortex trapping holes.
This version includes the complete resonator test chip layout with additional
ground plane holes to trap magnetic vortices, improving the performance of
superconducting quantum circuits.
Returns:
Component: Test chip with ground plane fill patterns.
"""
return fill_magnetic_vortices(component=resonator_test_chip())
if __name__ == "__main__":
from qpdk import PDK
PDK.activate()
# Create and display the filled version
filled_chip = filled_resonator_test_chip()
filled_chip.show()