"""S-parameter models for couplers."""
import jax.numpy as jnp
import sax
from jax.typing import ArrayLike
from skrf import Frequency
from qpdk.models.generic import capacitor, tee
from qpdk.models.media import MediaCallable, cpw_media_skrf
from qpdk.models.waveguides import straight
[docs]
def coupler_straight(
f: ArrayLike = jnp.array([5e9]),
length: int | float = 20.0,
gap: int | float = 0.27, # noqa: ARG001
media: MediaCallable = cpw_media_skrf(),
) -> sax.SType:
"""S-parameter model for two coupled coplanar waveguides, :func:`~qpdk.cells.waveguides.coupler_straight`.
Args:
f: Array of frequency points in Hz
length: Physical length of coupling section in µm
gap: Gap between the coupled waveguides in µm
media: Function returning a scikit-rf :class:`~Media` object after called
with ``frequency=f``. If None, uses default CPW media.
Returns:
sax.SType: S-parameters dictionary
.. code::
o2──────▲───────o3
│gap
o1──────▼───────o4
"""
straight_settings = {"length": length / 2, "media": media}
capacitor_settings = {
"capacitance": 60e-15, # gap * 1e-18 * f, # TODO implement FEM simulation retrieval or use some paper
"z0": media(frequency=Frequency.from_f(f, unit="Hz")).z0,
}
# Create straight instances with shared settings
straight_instances = {
f"straight_{i}_{j}": {
"component": "straight",
"settings": straight_settings,
}
for i in [1, 2]
for j in [1, 2]
}
tee_instances = {f"tee_{i}": {"component": "tee"} for i in [1, 2]}
circuit, _ = sax.circuit(
netlist={
"instances": {
**straight_instances,
**tee_instances,
"capacitor": {
"component": "capacitor",
"settings": capacitor_settings,
},
},
"connections": {
"straight_1_1,o1": "tee_1,o1",
"straight_1_2,o1": "tee_1,o2",
"straight_2_1,o1": "tee_2,o1",
"straight_2_2,o1": "tee_2,o2",
"tee_1,o3": "capacitor,o1",
"tee_2,o3": "capacitor,o2",
},
"ports": {
"o2": "straight_1_1,o2",
"o3": "straight_1_2,o2",
"o1": "straight_2_1,o2",
"o4": "straight_2_2,o2",
},
},
models={
"straight": straight,
"capacitor": capacitor,
"tee": tee,
},
)
return circuit(f=f)
if __name__ == "__main__":
from matplotlib import pyplot as plt
# Define frequency range from 1 GHz to 10 GHz with 201 points
f = jnp.linspace(1e9, 10e9, 201)
# Calculate coupler S-parameters for a 20 um straight coupler with 0.27 um gap
coupler = coupler_straight(f=f, length=20, gap=0.27)
# Create figure with single plot for comparison
fig, ax = plt.subplots(1, 1, figsize=(10, 6))
# Define S-parameters to plot
s_params = [
(("o1", "o1"), "$S_{11}$ Reflection"),
(("o1", "o2"), "$S_{12}$ Coupled branch 1"),
(("o1", "o3"), "$S_{13}$ Coupled branch 2"),
(("o1", "o4"), "$S_{14}$ Insertion loss (direct through)"),
]
# Plot each S-parameter for both coupler implementations
default_color_cycler = plt.cm.tab10.colors
for idx, (ports, label) in enumerate(s_params):
color = default_color_cycler[idx % len(default_color_cycler)]
# Plot both implementations with same color but different linestyles
ax.plot(
f / 1e9,
20 * jnp.log10(jnp.abs(coupler[ports])),
linestyle="-",
color=color,
label=f"{label} coupler_straight",
)
# Configure plot
ax.set_xlabel("Frequency [GHz]")
ax.set_ylabel("$S$-parameter [dB]")
ax.set_title(r"$S$-parameters: $\mathtt{coupler\_straight}$$")
ax.grid(True, which="both")
ax.legend()
plt.tight_layout()
plt.show(block=False)