Coverage for qpdk / models / resonator.py: 100%
49 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"""Resonators."""
3from typing import Any
5import jax.numpy as jnp
6import sax
7from gdsfactory.typings import CrossSectionSpec
8from sax.models.rf import capacitor, electrical_open, electrical_short, tee
10from qpdk.helper import deprecated
11from qpdk.models.constants import DEFAULT_FREQUENCY, c_0
12from qpdk.models.couplers import cpw_cpw_coupling_capacitance
13from qpdk.models.cpw import (
14 cpw_parameters,
15 cpw_z0_from_cross_section,
16 get_cpw_dimensions,
17)
18from qpdk.models.waveguides import straight, straight_shorted
21def quarter_wave_resonator_coupled(
22 f: sax.FloatArrayLike = DEFAULT_FREQUENCY,
23 length: float = 5000.0,
24 coupling_gap: float = 0.27,
25 coupling_straight_length: float = 20,
26 cross_section: CrossSectionSpec = "cpw",
27) -> sax.SDict:
28 """Model for a quarter-wave coplanar waveguide resonator coupled to a probeline.
30 Args:
31 cross_section: The cross-section of the CPW.
32 f: Frequency in Hz at which to evaluate the S-parameters.
33 length: Total length of the resonator in μm.
34 coupling_gap: Gap between the resonator and the probeline in μm.
35 coupling_straight_length: Length of the coupling section in μm.
37 Returns:
38 sax.SDict: S-parameters dictionary
39 """
40 f_arr = jnp.asarray(f)
42 instances = {
43 "resonator": resonator_coupled(
44 f=f_arr,
45 length=length,
46 coupling_gap=coupling_gap,
47 coupling_straight_length=coupling_straight_length,
48 cross_section=cross_section,
49 open_start=True,
50 open_end=False,
51 ),
52 "short": electrical_short(f=f_arr),
53 }
55 connections = {
56 "resonator,resonator_o2": "short,o1",
57 }
59 ports = {
60 "coupling_o1": "resonator,coupling_o1",
61 "coupling_o2": "resonator,coupling_o2",
62 "resonator_o1": "resonator,resonator_o1",
63 }
65 return sax.evaluate_circuit_fg((connections, ports), instances)
68def resonator_coupled(
69 f: sax.FloatArrayLike = DEFAULT_FREQUENCY,
70 length: float = 5000.0,
71 coupling_gap: float = 0.27,
72 coupling_straight_length: float = 20,
73 cross_section: CrossSectionSpec = "cpw",
74 open_start: bool = True,
75 open_end: bool = False,
76) -> sax.SDict:
77 """Model for a coplanar waveguide resonator coupled to a probeline.
79 Args:
80 cross_section: The cross-section of the CPW.
81 f: Frequency in Hz at which to evaluate the S-parameters.
82 length: Total length of the resonator in μm.
83 coupling_gap: Gap between the resonator and the probeline in μm.
84 coupling_straight_length: Length of the coupling section in μm.
85 open_start: If True, adds an electrical open at the start.
86 open_end: If True, adds an electrical open at the end.
88 Returns:
89 sax.SDict: S-parameters dictionary with 4 ports.
90 """
91 f_arr = jnp.asarray(f)
93 capacitor_settings = {
94 "capacitance": cpw_cpw_coupling_capacitance(
95 f_arr, coupling_straight_length, coupling_gap, cross_section
96 ),
97 "z0": cpw_z0_from_cross_section(cross_section, f_arr),
98 }
100 instances = {
101 "coupling_1": straight(
102 f=f_arr, length=coupling_straight_length / 2, cross_section=cross_section
103 ),
104 "coupling_2": straight(
105 f=f_arr, length=coupling_straight_length / 2, cross_section=cross_section
106 ),
107 "resonator_1": straight(
108 f=f_arr, length=coupling_straight_length / 2, cross_section=cross_section
109 ),
110 "resonator_2": straight(
111 f=f_arr,
112 length=length - coupling_straight_length / 2,
113 cross_section=cross_section,
114 ),
115 "tee_1": tee(f=f_arr),
116 "tee_2": tee(f=f_arr),
117 "capacitor": capacitor(f=f_arr, **capacitor_settings),
118 }
120 connections = {
121 "coupling_1,o2": "tee_1,o1",
122 "coupling_2,o1": "tee_1,o2",
123 "resonator_1,o2": "tee_2,o1",
124 "resonator_2,o1": "tee_2,o2",
125 "tee_1,o3": "capacitor,o1",
126 "tee_2,o3": "capacitor,o2",
127 }
129 ports = {
130 "coupling_o1": "coupling_1,o1",
131 "coupling_o2": "coupling_2,o2",
132 }
134 if open_start:
135 instances["open_start_term"] = electrical_open(f=f_arr, n_ports=2)
136 connections["resonator_1,o1"] = "open_start_term,o1"
137 ports["resonator_o1"] = "open_start_term,o2"
138 else:
139 ports["resonator_o1"] = "resonator_1,o1"
141 if open_end:
142 instances["open_end_term"] = electrical_open(f=f_arr, n_ports=2)
143 connections["resonator_2,o2"] = "open_end_term,o1"
144 ports["resonator_o2"] = "open_end_term,o2"
145 else:
146 ports["resonator_o2"] = "resonator_2,o2"
148 return sax.evaluate_circuit_fg((connections, ports), instances)
151def resonator_frequency(
152 *,
153 length: float,
154 epsilon_eff: float | None = None,
155 media: Any = None,
156 cross_section: CrossSectionSpec = "cpw",
157 is_quarter_wave: bool = True,
158) -> float:
159 r"""Calculate the resonance frequency of a quarter- or half-wave CPW resonator.
161 .. math::
163 \begin{aligned}
164 f &= \frac{v_p}{4L} \mathtt{ (quarter-wave resonator)} \\
165 f &= \frac{v_p}{2L} \mathtt{ (half-wave resonator)}
166 \end{aligned}
168 The phase velocity is :math:`v_p = c_0 / \sqrt{\varepsilon_{\mathrm{eff}}}`.
170 See :cite:`simonsCoplanarWaveguideCircuits2001,m.pozarMicrowaveEngineering2012` for details.
172 Args:
173 length: Length of the resonator in μm.
174 epsilon_eff: Effective permittivity. If ``None`` (default),
175 computed from *cross_section* using :func:`~qpdk.models.cpw.cpw_parameters`.
176 media: Deprecated. Use *epsilon_eff* or *cross_section* instead.
177 cross_section: Cross-section specification (used only when
178 *epsilon_eff* and *media* are not provided).
179 is_quarter_wave: If True, calculates for a quarter-wave resonator; if False, for a half-wave resonator.
180 default is True.
182 Returns:
183 float: Resonance frequency in Hz.
184 """
185 if epsilon_eff is None:
186 if media is not None:
187 deprecated(
188 "The 'media' argument is deprecated. Use 'epsilon_eff' or 'cross_section' instead."
189 )(lambda: None)()
190 epsilon_eff = float(jnp.real(jnp.mean(media.ep_r)))
191 else:
192 width, gap = get_cpw_dimensions(cross_section)
193 epsilon_eff, _z0 = cpw_parameters(width, gap)
195 v_p = c_0 / jnp.sqrt(epsilon_eff)
196 coefficient = 4 if is_quarter_wave else 2
197 return float(jnp.squeeze(v_p / (coefficient * length * 1e-6)))
200def resonator(
201 f: sax.FloatArrayLike = DEFAULT_FREQUENCY,
202 length: sax.Float = 1000,
203 cross_section: CrossSectionSpec = "cpw",
204) -> sax.SType:
205 """S-parameter model for a simple transmission line resonator.
207 Args:
208 f: Array of frequency points in Hz
209 length: Physical length in µm
210 cross_section: The cross-section of the waveguide.
212 Returns:
213 sax.SType: S-parameters dictionary
214 """
215 return straight(f=f, length=length, cross_section=cross_section)
218def resonator_half_wave(
219 f: sax.FloatArrayLike = DEFAULT_FREQUENCY,
220 length: sax.Float = 1000,
221 cross_section: CrossSectionSpec = "cpw",
222) -> sax.SType:
223 """S-parameter model for a half-wave resonator (open at both ends).
225 Args:
226 f: Array of frequency points in Hz
227 length: Physical length in µm
228 cross_section: The cross-section of the waveguide.
230 Returns:
231 sax.SType: S-parameters dictionary
232 """
233 return straight(f=f, length=length, cross_section=cross_section)
236def resonator_quarter_wave(
237 f: sax.FloatArrayLike = DEFAULT_FREQUENCY,
238 length: sax.Float = 1000,
239 cross_section: CrossSectionSpec = "cpw",
240) -> sax.SType:
241 """S-parameter model for a quarter-wave resonator (shorted at one end).
243 Args:
244 f: Array of frequency points in Hz
245 length: Physical length in µm
246 cross_section: The cross-section of the waveguide.
248 Returns:
249 sax.SType: S-parameters dictionary
250 """
251 return straight_shorted(f=f, length=length, cross_section=cross_section)