"""
Models that transform the ports or layout of another model.
"""
import jax.numpy as jnp
from pmrf.frequency import Frequency
from pmrf.models.model import Model
from pmrf.field import field
[docs]
class Renumbered(Model, transparent=True):
"""
A container that re-numbers the ports of a given `Model`.
This is useful for creating complex network topologies by explicitly
re-mapping the port indices of a sub-network.
Attributes
----------
model : Model
The underlying model to renumber.
from_ports : tuple[int]
The original port indices that map to `to_ports`.
to_ports : tuple[int]
The new port indices. Can be `None`, in which case `from_ports`
must contain exactly two ports to be swapped.
"""
model: Model
from_ports: tuple[int]
to_ports: tuple[int] = None
def __post_init__(self):
model = self.model
if self.to_ports is None:
if len(self.from_ports) != 2:
raise Exception("from_ports must have length==2 if to_ports is None")
self.to_ports = (self.from_ports[1], self.from_ports[0])
if model.primary_property == 'a' and len(self.from_ports) != 2 and len(self.to_ports) != 2:
raise ValueError("(from_ports, to_ports) must be either (0, 1) or (1, 0) for 'a' primary networks")
if len(self.from_ports) != len(self.to_ports):
raise ValueError("from_ports and to_ports must have the same length for Renumbered")
[docs]
def renumber(self, p: jnp.ndarray) -> jnp.ndarray:
"""
Applies the port renumbering to a parameter matrix.
Parameters
----------
p : jnp.ndarray
The parameter matrix to renumber (e.g., S-parameters).
Returns
-------
jnp.ndarray
The renumbered parameter matrix.
"""
p_new = p.copy()
p_new = p_new.at[:, self.to_ports, :].set(p[:, self.from_ports, :])
p_new = p_new.at[:, :, self.to_ports].set(p_new[:, :, self.from_ports])
return p_new
[docs]
def a(self, freq: Frequency) -> jnp.ndarray:
return self.renumber(self.model.a(freq))
[docs]
def s(self, freq: Frequency) -> jnp.ndarray:
return self.renumber(self.model.s(freq))
[docs]
def y(self, freq: Frequency) -> jnp.ndarray:
return self.renumber(self.model.y(freq))
[docs]
def z(self, freq: Frequency) -> jnp.ndarray:
return self.renumber(self.model.z(freq))
[docs]
class Flipped(Renumbered):
"""
A model container that flips the ports of a multi-port network.
For a 2-port network, this is equivalent to swapping port 1 and port 2.
For a 4-port network, ports (1,2) are swapped with (3,4), and so on.
This is a convenient specialization of the `Renumbered` model.
"""
to_ports: tuple[int] = field(init=False)
from_ports: tuple[int] = field(init=False)
def __post_init__(self):
if self.model.nports % 2 != 0:
raise ValueError("You can only flip multiple-of-two-port Networks")
n = int(self.model.nports / 2)
self.to_ports = tuple(range(0, 2 * n))
self.from_ports = tuple(range(n, 2 * n)) + tuple(range(0, n))
super().__post_init__()
self.name = 'flipped'
[docs]
class Stacked(Model, transparent=True):
"""
A container that stacks multiple models in a block-diagonal fashion.
This combines several `Model` objects into a single, larger model where
the individual S-parameter matrices are placed along the diagonal of the
combined S-parameter matrix. This represents a set of unconnected
networks treated as a single component.
Attributes
----------
models : tuple[Model, ...]
The models to stack.
"""
models: tuple[Model, ...]
[docs]
def s(self, freq: Frequency) -> jnp.ndarray:
num_ports = sum(model.nports for model in self.models)
s = jnp.zeros((freq.npoints, num_ports, num_ports), dtype=jnp.complex128)
i = 0
for submodel in self.models:
s_sub = submodel.s(freq)
n_sub = submodel.nports
s = s.at[:,i:i+n_sub,i:i+n_sub].set(s_sub)
i += n_sub
return s