import jax.numpy as jnp
from pmrf.parameters import Parameter
from pmrf.models.model import Model
from pmrf.frequency import Frequency
[docs]
class Load(Model):
"""
**Overview**
An abstract base class for all 1-port terminating load models.
This class establishes the interface for 1-port networks, which are
defined by their reflection coefficient, `gamma`. Subclasses must implement
the `gamma` property.
"""
nports: int = 1
@property
def gamma(self) -> float | jnp.ndarray:
"""The complex reflection coefficient (Gamma) of the load."""
raise NotImplementedError("Subclasses of Load must implement the 'gamma' property.")
[docs]
def s(self, freq: Frequency) -> jnp.ndarray:
"""Calculates the 1-port S-parameter matrix.
Args:
freq (Frequency): The frequency axis for the calculation.
Returns:
np.ndarray: The resultant 1x1 S-parameter matrix.
"""
gamma, nports = self.gamma, self.nports
# Create a frequency-dependent 1x1 matrix from the scalar gamma
s = jnp.array(gamma).reshape(-1, 1, 1) * \
jnp.eye(nports, dtype=jnp.complex128).reshape((-1, nports, nports)).\
repeat(freq.npoints, 0)
return s
[docs]
class Match(Load):
"""
**Overview**
An ideal matched load (perfect termination).
"""
@property
def gamma(self) -> float | jnp.ndarray:
"""The reflection coefficient of a matched load, which is always 0."""
return 0.0
[docs]
class Short(Load):
"""
**Overview**
An ideal short circuit.
"""
@property
def gamma(self) -> float | jnp.ndarray:
"""The reflection coefficient of a short circuit, which is always -1."""
return -1.0
[docs]
class Open(Load):
"""
**Overview**
An ideal open circuit.
"""
@property
def gamma(self) -> float | jnp.ndarray:
"""The reflection coefficient of an open circuit, which is always +1."""
return 1.0
[docs]
class Capacitor(Model):
"""
**Overview**
A 2-port model of a series capacitor.
**Example**
```python
import pmrf as prf
# Create a 1 pF series capacitor
c_series = prf.models.Capacitor(C=1e-12)
# Terminate the capacitor in a short to create a 1-port shunt capacitor
c_shunt = c_series.terminated(prf.models.Short())
freq = prf.Frequency(start=1, stop=10, npoints=101, unit='ghz')
s = c_shunt.s(freq)
print(f"Shunt capacitor has {c_shunt.nports} port.")
```
"""
C: Parameter = 1.0
[docs]
def s(self, freq: Frequency) -> jnp.ndarray:
"""Calculates the S-parameters of a series capacitor.
Args:
freq (Frequency): The frequency axis for the calculation.
Returns:
np.ndarray: The resultant 2x2 S-parameter matrix.
"""
w = freq.w
C = self.C
z0_0 = z0_1 = self.z0
denom = 1.0 + 1j * w * C * (z0_0 + z0_1)
s11 = (1.0 - 1j * w * C * (jnp.conj(z0_0) - z0_1) ) / denom
s22 = (1.0 - 1j * w * C * (jnp.conj(z0_1) - z0_0) ) / denom
s12 = s21 = (2j * w * C * (z0_0.real * z0_1.real)**0.5) / denom
s = jnp.array([
[s11, s12],
[s21, s22]
]).transpose(2, 0, 1)
return s
[docs]
class Inductor(Model):
"""
**Overview**
A 2-port model of a series inductor.
**Example**
```python
import pmrf as prf
# Create a 1 nH series inductor
l_series = prf.models.Inductor(L=1e-9)
freq = prf.Frequency(start=1, stop=10, npoints=101, unit='ghz')
s = l_series.s(freq)
print(f"Series inductor has {l_series.nports} ports.")
```
"""
L: Parameter = 1.0
def __post_init__(self):
self.name = 'inductor'
[docs]
def s(self, freq: Frequency) -> jnp.ndarray:
"""Calculates the S-parameters of a series inductor.
Args:
freq (Frequency): The frequency axis for the calculation.
Returns:
np.ndarray: The resultant 2x2 S-parameter matrix.
"""
L = self.L
w = freq.w
z0_0 = z0_1 = self.z0
denom = (1j * w * L) + (z0_0 + z0_1)
s11 = (1j * w * L - jnp.conj(z0_0) + z0_1) / denom
s22 = (1j * w * L + z0_0 - jnp.conj(z0_1)) / denom
s12 = s21 = 2 * (z0_0.real * z0_1.real)**0.5 / denom
s = jnp.array([
[s11, s12],
[s21, s22]
]).transpose(2, 0, 1)
return s
[docs]
class Resistor(Model):
"""
**Overview**
A 2-port model of a series resistor.
**Example**
```python
import pmrf as prf
# Create a 25-ohm series resistor (e.g., for an attenuator pad)
r_series = prf.models.Resistor(R=25.0)
freq = prf.Frequency(start=1, stop=10, npoints=101, unit='ghz')
s = r_series.s(freq)
print(f"Series resistor has {r_series.nports} ports.")
```
"""
R: Parameter = 1.0
[docs]
def s(self, freq: Frequency) -> jnp.ndarray:
"""Calculates the S-parameters of a series resistor.
Args:
freq (Frequency): The frequency axis for the calculation.
Returns:
np.ndarray: The resultant 2x2 S-parameter matrix.
"""
R = self.R
z0_0 = z0_1 = self.z0
ones = jnp.ones(freq.npoints, dtype=jnp.complex128)
denom = R + (z0_0 + z0_1)
s11 = ((R - jnp.conj(z0_0) + z0_1) / denom) * ones
s22 = ((R + z0_0 - jnp.conj(z0_1)) / denom) * ones
s12 = (2 * (z0_0.real * z0_1.real)**0.5 / denom) * ones
s21 = s12
s = jnp.array([
[s11, s12],
[s21, s22]
]).transpose(2, 0, 1)
return s
SHORT = Short()
OPEN = Open()
MATCH = Match()