"""Data structures for post-processing terminal component simulations to calculate S-matrices."""
from __future__ import annotations
from typing import TYPE_CHECKING, Optional, Union
import numpy as np
from pydantic import Field, TypeAdapter
from tidy3d.components.base import cached_property
from tidy3d.components.data.data_array import FreqDataArray
from tidy3d.components.microwave.base import MicrowaveBaseModel
from tidy3d.constants import C_0
from tidy3d.plugins.smatrix.component_modelers.terminal import TerminalComponentModeler
from tidy3d.plugins.smatrix.data.base import AbstractComponentModelerData
from tidy3d.plugins.smatrix.data.data_array import PortDataArray, TerminalPortDataArray
from tidy3d.plugins.smatrix.ports.types import LumpedPortType
from tidy3d.plugins.smatrix.types import SParamDef
from tidy3d.plugins.smatrix.utils import (
ab_to_s,
check_port_impedance_sign,
compute_F,
compute_port_VI,
compute_power_delivered_by_port,
compute_power_wave_amplitudes,
s_to_z,
)
if TYPE_CHECKING:
from tidy3d.components.data.monitor_data import MonitorData
from tidy3d.components.data.sim_data import SimulationData
from tidy3d.components.microwave.data.monitor_data import AntennaMetricsData
from tidy3d.plugins.smatrix.data.data_array import PortNameDataArray
from tidy3d.plugins.smatrix.types import NetworkIndex
# Accepted types for the renormalized_reference_impedance field.
# - complex: uniform impedance applied to all ports (diagonal matrix)
# - PortDataArray: per-port impedance with dims (f, port) → diagonal matrix
# - TerminalPortDataArray: full impedance matrix with dims (f, port_out, port_in)
RenormalizedReferenceImpedance = Union[complex, TerminalPortDataArray, PortDataArray]
_ref_impedance_adapter = TypeAdapter(RenormalizedReferenceImpedance)
[docs]
class MicrowaveSMatrixData(MicrowaveBaseModel):
"""Stores the computed S-matrix and reference impedances for the terminal ports."""
port_reference_impedances: Optional[TerminalPortDataArray] = Field(
None,
title="Port Reference Impedances",
description="Reference impedance matrix for each port used in the S-parameter calculation. "
"Has dimensions (f, port_out, port_in) to support coupled impedances from TerminalWavePort. "
"For WavePort and LumpedPort, the impedance matrix is diagonal.",
)
data: TerminalPortDataArray = Field(
title="S-Matrix Data",
description="An array containing the computed S-matrix of the device. The data is organized by terminal ports, representing the scattering parameters between them.",
)
s_param_def: SParamDef = Field(
"pseudo",
title="Scattering Parameter Definition",
description="Wave definition: 'pseudo', 'power', or 'symmetric_pseudo'.",
)
[docs]
class TerminalComponentModelerData(AbstractComponentModelerData, MicrowaveBaseModel):
"""
Data associated with a :class:`.TerminalComponentModeler` simulation run.
Notes
-----
This class serves as a data container for the results of a component modeler simulation,
with the original simulation definition, and port simulation data, and the solver log.
**S-Parameter Definitions**
The ``s_param_def`` parameter controls which wave definition is used to compute scattering
parameters. Three definitions are supported:
- ``"pseudo"`` (default): Pseudo-waves as defined by Marks and Williams [1]. Uses scaling
factor :math:`F = \\sqrt{\\text{Re}(Z)} / (2|Z|)`. Wave amplitudes are :math:`a = F(V + ZI)`
and :math:`b = F(V - ZI)`.
- ``"power"``: Power waves as defined by Kurokawa [3] and described in Pozar [2]. Uses
scaling factor :math:`F = 1 / (2\\sqrt{\\text{Re}(Z)})`. Wave amplitudes are
:math:`a = F(V + ZI)` and :math:`b = F(V - Z^*I)` where :math:`Z^*` is the complex
conjugate. Ensures :math:`|a|^2 - |b|^2` represents actual power flow.
- ``"symmetric_pseudo"``: Equivalent to pseudo-waves except for the scaling factor. Uses
:math:`F = 1 / (2\\sqrt{Z})` where the square root is complex. This choice of scaling
factor ensures the S-matrix will be symmetric when the simulated device is reciprocal.
**References**
.. [1] R. B. Marks and D. F. Williams, "A general waveguide circuit theory,"
J. Res. Natl. Inst. Stand. Technol., vol. 97, pp. 533, 1992.
.. [2] D. M. Pozar, Microwave Engineering, 4th ed. Hoboken, NJ, USA:
John Wiley & Sons, 2012.
.. [3] K. Kurokawa, "Power Waves and the Scattering Matrix," IEEE Trans.
Microwave Theory Tech., vol. 13, no. 2, pp. 194-202, March 1965.
"""
modeler: TerminalComponentModeler = Field(
...,
title="TerminalComponentModeler",
description="The original :class:`.TerminalComponentModeler` object that defines the simulation setup "
"and from which this data was generated.",
)
renormalized_reference_impedance: Optional[RenormalizedReferenceImpedance] = Field(
None,
title="Renormalized Reference Impedance",
description="When set, overrides port_reference_impedances for all S-matrix computations. "
"Accepts: complex (uniform), PortDataArray (per-port diagonal), "
"or TerminalPortDataArray (full matrix).",
)
[docs]
def renormalize(
self, reference_impedance: RenormalizedReferenceImpedance
) -> TerminalComponentModelerData:
"""Return a new instance whose S-matrix is renormalized to a different reference impedance.
The returned object recomputes wave amplitudes from the cached voltage/current
data using the new reference impedance, so all downstream methods
(:meth:`smatrix`, :meth:`s_to_z`, :meth:`compute_port_wave_amplitude_matrices`, etc.)
automatically reflect the new impedance.
Parameters
----------
reference_impedance : RenormalizedReferenceImpedance
The new reference impedance. Accepts:
- ``complex`` — uniform impedance applied to all ports (e.g. ``50``)
- :class:`.PortDataArray` — per-port impedance with dims ``(f, port)``
- :class:`.TerminalPortDataArray` — full impedance matrix ``(f, port_out, port_in)``
Returns
-------
:class:`.TerminalComponentModelerData`
A copy of this object with the new reference impedance applied.
Examples
--------
>>> s_50 = modeler_data.renormalize(50).smatrix() # doctest: +SKIP
"""
_ref_impedance_adapter.validate_python(reference_impedance)
return self.updated_copy(
renormalized_reference_impedance=reference_impedance,
deep=False,
validate=False,
)
def _build_reference_impedance_matrix(
self, value: RenormalizedReferenceImpedance
) -> TerminalPortDataArray:
"""Convert any accepted impedance input to a full ``TerminalPortDataArray``.
Parameters
----------
value : RenormalizedReferenceImpedance
The reference impedance value to convert.
Returns
-------
:class:`.TerminalPortDataArray`
Impedance matrix with dimensions ``(f, port_out, port_in)``.
"""
network_indices = list(self.modeler.matrix_indices_monitor)
num_ports = len(network_indices)
freqs = np.array(self.modeler.freqs)
num_freqs = len(freqs)
coords = {
"f": freqs,
"port_out": network_indices,
"port_in": network_indices,
}
if isinstance(value, TerminalPortDataArray):
return value
if isinstance(value, PortDataArray):
# Per-port diagonal: PortDataArray has dims (f, port)
values = np.zeros((num_freqs, num_ports, num_ports), dtype=complex)
for i, port_name in enumerate(network_indices):
values[:, i, i] = value.sel(port=port_name).values
return TerminalPortDataArray(values, coords=coords)
if isinstance(value, (int, float, complex)):
# Uniform scalar → diagonal matrix
values = np.zeros((num_freqs, num_ports, num_ports), dtype=complex)
for i in range(num_ports):
values[:, i, i] = complex(value)
return TerminalPortDataArray(values, coords=coords)
raise TypeError(
f"Unsupported type for renormalized_reference_impedance: {type(value).__name__}. "
"Expected complex, PortDataArray, TerminalPortDataArray, or 'Z0'."
)
[docs]
def smatrix(
self,
assume_ideal_excitation: Optional[bool] = None,
s_param_def: Optional[SParamDef] = None,
) -> MicrowaveSMatrixData:
"""Computes and returns the S-matrix and port reference impedances.
Parameters
----------
assume_ideal_excitation: If ``True``, assumes that exciting one port
does not produce incident waves at other ports. This simplifies the
S-matrix calculation and is required if not all ports are excited. If not
provided, ``modeler.assume_ideal_excitation`` is used.
s_param_def: Wave definition: "pseudo", "power", or "symmetric_pseudo".
If not provided, ``modeler.s_param_def`` is used.
See :class:`.TerminalComponentModeler` for details.
Returns
-------
:class:`.MicrowaveSMatrixData`
Container with the computed S-parameters and the port reference impedances.
"""
from tidy3d.plugins.smatrix.analysis.terminal import terminal_construct_smatrix
terminal_port_data = terminal_construct_smatrix(
modeler_data=self,
assume_ideal_excitation=assume_ideal_excitation
if (assume_ideal_excitation is not None)
else self.modeler.assume_ideal_excitation,
s_param_def=s_param_def if (s_param_def is not None) else self.modeler.s_param_def,
)
smatrix_data = MicrowaveSMatrixData(
data=terminal_port_data,
port_reference_impedances=self.port_reference_impedances,
s_param_def=s_param_def if (s_param_def is not None) else self.modeler.s_param_def,
)
return smatrix_data
[docs]
def change_port_reference_planes(
self, smatrix: MicrowaveSMatrixData, port_shifts: PortNameDataArray = None
) -> MicrowaveSMatrixData:
"""
Performs S-parameter de-embedding by shifting reference planes ``port_shifts`` um.
Parameters
----------
smatrix : :class:`.MicrowaveSMatrixData`
S-parameters before reference planes are shifted.
port_shifts : :class:`.PortNameDataArray`
Data array of shifts of wave ports' reference planes.
The sign of a port shift reflects direction with respect to the axis normal to a ``WavePort`` plane:
E.g.: ``PortNameDataArray(data=-a, coords={"port": "WP1"})`` defines a shift in the first ``WavePort`` by
``a`` um in the direction opposite to the positive axis direction (the axis normal to the port plane).
Returns
-------
:class:`MicrowaveSMatrixData`
De-embedded S-parameters with respect to updated reference frames.
"""
# get s-parameters with respect to current `WavePort` locations
S_matrix = smatrix.data.values
S_new = np.zeros_like(S_matrix, dtype=complex)
N_freq, N_ports, _ = S_matrix.shape
# pre-allocate memory for effective propagation constants
kvecs = np.zeros((N_freq, N_ports), dtype=complex)
shifts_vec = np.zeros(N_ports)
directions_vec = np.ones(N_ports)
port_idxs = []
n_complex_new = []
# extract raw data
key = self.data.keys_tuple[0]
data = self.data[key].data
ports = self.modeler.ports
# get port names and names of ports to be shifted
port_names = [port.name for port in ports]
shift_names = port_shifts.coords["port"].values
# Build a mapping for quick lookup from monitor name to monitor data
mode_map = {mode_data.monitor.name: mode_data for mode_data in data}
# form a numpy vector of port shifts
for shift_name in shift_names:
# ensure that port shifts were defined for valid ports
if shift_name not in port_names:
raise ValueError(
"The specified port could not be found in the simulation! "
f"Please, make sure the port name is from the following list {port_names}"
)
# get the port by the name
port = self.modeler.get_port_by_name(shift_name)
# if de-embedding is requested for lumped port
if isinstance(port, LumpedPortType):
raise ValueError(
"De-embedding currently supports only 'WavePort' instances. "
f"Received type: '{type(port).__name__}'."
)
# alternatively we can send a warning and set `shifts_vector[index]` to 0.
# shifts_vector[index] = 0.0
else:
# Collect corresponding mode_data
mode_data = mode_map[port._mode_monitor_name]
for mode_index in port._mode_indices(
mode_spec=self.modeler._resolved_mode_specs.get(port.name)
):
network_index = self.modeler.network_index(port, mode_index)
idx = smatrix.data.indexes["port_in"].get_loc(network_index)
shifts_vec[idx] = port_shifts.sel(port=shift_name).values
directions_vec[idx] = -1 if port.direction == "-" else 1
port_idxs.append(idx)
n_complex = mode_data.n_complex.sel(mode_index=mode_index)
n_complex_new.append(np.squeeze(n_complex.data))
# flatten port shift vector
shifts_vec = np.ravel(shifts_vec)
directions_vec = np.ravel(directions_vec)
# Convert to stacked arrays
freqs = np.array(self.modeler.freqs)
n_complex_new = np.array(n_complex_new).T
# construct transformation matrix P_inv
kvecs[:, port_idxs] = 2 * np.pi * freqs[:, np.newaxis] * n_complex_new / C_0
phase = -kvecs * shifts_vec * directions_vec
P_inv = np.exp(1j * phase)
# de-embed S-parameters: S_new = P_inv @ S_matrix @ P_inv
S_new = S_matrix * P_inv[:, :, np.newaxis] * P_inv[:, np.newaxis, :]
# create a new Port Data Array
smat_data = TerminalPortDataArray(S_new, coords=smatrix.data.coords)
return smatrix.updated_copy(data=smat_data)
[docs]
def smatrix_deembedded(self, port_shifts: np.ndarray = None) -> MicrowaveSMatrixData:
"""Interface function returns de-embedded S-parameter matrix."""
return self.change_port_reference_planes(self.smatrix(), port_shifts=port_shifts)
def _monitor_data_at_port_amplitude(
self,
port_index: NetworkIndex,
monitor_name: str,
a_port: Union[FreqDataArray, complex],
a_raw_port: FreqDataArray,
) -> MonitorData:
"""Normalize monitor data to a desired complex amplitude at a specific port.
This method scales the monitor data so that the incident wave amplitude at the
specified port matches the desired value, where :math:`\frac{1}{2}|a|^2` represents
the power incident from the port into the system.
Parameters
----------
port_index : NetworkIndex
The port at which to normalize the amplitude.
monitor_name : str
Name of the monitor to normalize.
a_port : Union[:class:`.FreqDataArray`, complex]
Desired complex amplitude at the port. If a complex number is provided,
it is applied uniformly across all frequencies.
a_raw_port : :class:`.FreqDataArray`
Raw incident wave amplitude at the port from the simulation, used as
the reference for scaling.
Returns
-------
:class:`.MonitorData`
Normalized monitor data scaled to the desired port amplitude.
"""
task_name = self.modeler.task_name_from_index(port_index)
sim_data_port = self.data[task_name]
monitor_data = sim_data_port[monitor_name]
if not isinstance(a_port, FreqDataArray):
freqs = list(monitor_data.monitor.freqs)
array_vals = a_port * np.ones(len(freqs))
a_port = FreqDataArray(array_vals, coords={"f": freqs})
scale_array = a_port / a_raw_port
return monitor_data.scale_fields_by_freq_array(scale_array, method="nearest")
[docs]
def get_antenna_metrics_data(
self,
port_amplitudes: Optional[dict[NetworkIndex, complex]] = None,
monitor_name: Optional[str] = None,
) -> AntennaMetricsData:
"""Calculate antenna parameters using superposition of fields from multiple port excitations.
The method computes the radiated far fields and port excitation power wave amplitudes
for a superposition of port excitations, which can be used to analyze antenna radiation
characteristics.
Note
----
The ``NetworkIndex`` identifies a single excitation in the modeled device, so it represents
a :class:`.LumpedPort` or a single mode from a :class:`.WavePort`. Use the static method
:meth:`.TerminalComponentModeler.network_index` to convert port and optional mode index
into the appropriate ``NetworkIndex`` for use in the ``port_amplitudes`` dictionary.
Parameters
----------
port_amplitudes : dict[NetworkIndex, complex] = None
Dictionary mapping a network index to their desired excitation amplitudes. For each network port,
:math:`\\frac{1}{2}|a|^2` represents the incident power from that port into the system.
If ``None``, uses only the first port without any scaling of the raw simulation data. When
``None`` is passed as a port amplitude, the raw simulation data is used for that port. Note
that in this method ``a`` represents the incident wave amplitude using the power wave definition
in [2].
monitor_name : str
Name of the :class:`.DirectivityMonitor` to use for calculating far fields.
If None, uses the first monitor in `radiation_monitors`.
Returns
-------
:class:`.AntennaMetricsData`
Container with antenna parameters including directivity, gain, and radiation efficiency,
computed from the superposition of fields from all excited ports.
"""
from tidy3d.plugins.smatrix.analysis.antenna import get_antenna_metrics_data
antenna_metrics_data = get_antenna_metrics_data(
terminal_component_modeler_data=self,
port_amplitudes=port_amplitudes,
monitor_name=monitor_name,
)
return antenna_metrics_data
@cached_property
def port_reference_impedances(self) -> TerminalPortDataArray:
"""Calculates the reference impedance matrix for each port across all frequencies.
This function determines the characteristic impedance for every port defined
in the modeler. It returns a matrix with dimensions (f, port_out, port_in)
to support coupled impedances from :class:`.TerminalWavePort`. For
:class:`.WavePort` and :class:`.LumpedPort`, the impedance matrix is diagonal.
When :attr:`renormalized_reference_impedance` is set, returns the
user-specified impedance instead of the simulation-derived one.
Returns:
A :class:`.TerminalPortDataArray` containing the complex impedance matrix
with dimensions (f, port_out, port_in) for each frequency.
"""
if self.renormalized_reference_impedance is not None:
return self._build_reference_impedance_matrix(self.renormalized_reference_impedance)
from tidy3d.plugins.smatrix.analysis.terminal import port_reference_impedances
return port_reference_impedances(self)
[docs]
def compute_wave_amplitudes_at_each_port(
self,
sim_data: SimulationData,
port_reference_impedances: Optional[TerminalPortDataArray] = None,
s_param_def: SParamDef = "pseudo",
) -> tuple[PortDataArray, PortDataArray]:
"""Compute the incident and reflected amplitudes at each port.
The computed amplitudes have not been normalized.
Parameters
----------
sim_data : :class:`.SimulationData`
Results from the simulation.
port_reference_impedances : :class:`.TerminalPortDataArray`, optional
Reference impedance matrix with dimensions (f, port_out, port_in).
If not provided, it is computed from the cached
property :meth:`.port_reference_impedances`. Defaults to ``None``.
s_param_def : SParamDef
Wave definition: "pseudo", "power", or "symmetric_pseudo".
See :class:`.TerminalComponentModeler` for details.
Returns
-------
tuple[:class:`.PortDataArray`, :class:`.PortDataArray`]
Incident (a) and reflected (b) wave amplitudes at each port.
"""
from tidy3d.plugins.smatrix.analysis.terminal import compute_wave_amplitudes_at_each_port
port_reference_impedances_i = (
port_reference_impedances
if port_reference_impedances is not None
else self.port_reference_impedances
)
return compute_wave_amplitudes_at_each_port(
modeler=self.modeler,
port_reference_impedances=port_reference_impedances_i,
sim_data=sim_data,
s_param_def=s_param_def,
)
[docs]
def compute_power_wave_amplitudes_at_each_port(
self,
sim_data: SimulationData,
port_reference_impedances: Optional[TerminalPortDataArray] = None,
) -> tuple[PortDataArray, PortDataArray]:
"""Compute the incident and reflected power wave amplitudes at each port.
The computed amplitudes have not been normalized.
Parameters
----------
sim_data : :class:`.SimulationData`
Results from the simulation.
port_reference_impedances : :class:`.TerminalPortDataArray`, optional
Reference impedance matrix with dimensions (f, port_out, port_in).
If not provided, it is computed from the cached
property :meth:`.port_reference_impedances`. Defaults to ``None``.
Returns
-------
tuple[:class:`.PortDataArray`, :class:`.PortDataArray`]
Incident (a) and reflected (b) power wave amplitudes at each port.
"""
return self.compute_wave_amplitudes_at_each_port(
sim_data, port_reference_impedances, s_param_def="power"
)
[docs]
def s_to_z(
self,
assume_ideal_excitation: Optional[bool] = None,
s_param_def: SParamDef = "pseudo",
) -> TerminalPortDataArray:
"""Converts the S-matrix to the Z-matrix using the port reference impedances.
This method first computes the S-matrix of the device and then transforms it into the
corresponding impedance matrix (Z-matrix), using the reference impedances from
:meth:`port_reference_impedances`.
Parameters
----------
assume_ideal_excitation: If ``True``, assumes that exciting one port
does not produce incident waves at other ports. This simplifies the
S-matrix calculation and is required if not all ports are excited. If not
provided, ``modeler.assume_ideal_excitation`` is used.
s_param_def : SParamDef, optional
Wave definition: "pseudo", "power", or "symmetric_pseudo". Default is "pseudo".
See :class:`.TerminalComponentModeler` for details.
Returns
-------
DataArray
The computed impedance (Z) matrix, with dimensions corresponding to the ports of
the device.
Examples
--------
>>> z_matrix = component_modeler_data.s_to_z() # doctest: +SKIP
>>> z_11 = z_matrix.sel(port_out="port_1@0", port_in="port_1@0") # doctest: +SKIP
See Also
--------
smatrix : Computes the scattering matrix.
"""
s_matrix = self.smatrix(
assume_ideal_excitation=assume_ideal_excitation, s_param_def=s_param_def
)
return s_to_z(
s_matrix=s_matrix.data,
reference=self.port_reference_impedances,
s_param_def=s_param_def,
)
@cached_property
def port_voltage_current_matrices(self) -> tuple[TerminalPortDataArray, TerminalPortDataArray]:
"""Compute voltage and current matrices for all port combinations.
This method returns two matrices containing the voltage and current values computed
across all frequency points and port combinations. The matrices represent the response
at each output port when each input port is excited individually.
Returns
-------
tuple[:class:`.TerminalPortDataArray`, :class:`.TerminalPortDataArray`]
A tuple containing the voltage matrix and current matrix. Each matrix has dimensions
(f, port_out, port_in) representing the voltage/current response at
each output port due to excitation at each input port.
"""
from tidy3d.plugins.smatrix.analysis.terminal import (
_compute_port_voltages_currents,
)
ports_in = list(self.modeler.matrix_indices_run_sim)
ports_out = list(self.modeler.matrix_indices_monitor)
freqs = self.modeler.freqs
values = np.zeros(
(len(freqs), len(ports_out), len(ports_in)),
dtype=complex,
)
coords = {
"f": np.array(freqs),
"port_out": ports_out,
"port_in": ports_in,
}
port_voltage_matrix = TerminalPortDataArray(values, coords=coords)
port_current_matrix = port_voltage_matrix.copy(deep=True)
for source_index in self.modeler.matrix_indices_run_sim:
task_name = self.modeler.task_name_from_index(source_index)
sim_data = self.data[task_name]
port_voltages, port_currents = _compute_port_voltages_currents(self.modeler, sim_data)
indexer = {"port_in": source_index}
port_voltage_matrix = port_voltage_matrix._with_updated_data(
data=port_voltages.data, coords=indexer
)
port_current_matrix = port_current_matrix._with_updated_data(
data=port_currents.data, coords=indexer
)
return port_voltage_matrix, port_current_matrix
[docs]
def compute_port_wave_amplitude_matrices(
self,
s_param_def: SParamDef = "pseudo",
) -> tuple[TerminalPortDataArray, TerminalPortDataArray]:
"""Compute wave amplitude matrices for all port combinations.
This method computes the incident (a) and reflected (b) wave amplitude matrices
for all frequency points and port combinations using the specified wave definition.
The matrices represent the forward and backward traveling wave amplitudes at each
output port when each input port is excited individually.
Parameters
----------
s_param_def : SParamDef, optional
Wave definition: "pseudo", "power", or "symmetric_pseudo". Default is "pseudo".
See :class:`.TerminalComponentModeler` for details.
Returns
-------
tuple[:class:`.TerminalPortDataArray`, :class:`.TerminalPortDataArray`]
A tuple containing the incident (a) and reflected (b) wave amplitude matrices.
Each matrix has dimensions (f, port_out, port_in) representing
the wave amplitudes at each output port due to excitation at each input port.
"""
from tidy3d.plugins.smatrix.analysis.terminal import (
_compute_wave_amplitudes_from_VI,
)
port_voltage_matrix, port_current_matrix = self.port_voltage_current_matrices
a_matrix = port_voltage_matrix.copy(deep=True)
b_matrix = port_voltage_matrix.copy(deep=True)
for source_index in self.modeler.matrix_indices_run_sim:
port_a, port_b = _compute_wave_amplitudes_from_VI(
self.port_reference_impedances,
port_voltage_matrix.sel(port_in=source_index, drop=True),
port_current_matrix.sel(port_in=source_index, drop=True),
s_param_def=s_param_def,
)
indexer = {"port_in": source_index}
a_matrix = a_matrix._with_updated_data(data=port_a.data, coords=indexer)
b_matrix = b_matrix._with_updated_data(data=port_b.data, coords=indexer)
return a_matrix, b_matrix
@cached_property
def port_pseudo_wave_matrices(self) -> tuple[TerminalPortDataArray, TerminalPortDataArray]:
"""Compute pseudo-wave amplitude matrices for all port combinations.
This method returns the incident (a) and reflected (b) pseudo-wave amplitude matrices
computed using the pseudo-wave definition from Marks and Williams [1]. The matrices
represent the forward and backward traveling wave amplitudes at each output port when
each input port is excited individually.
Returns
-------
tuple[:class:`.TerminalPortDataArray`, :class:`.TerminalPortDataArray`]
A tuple containing the incident (a) and reflected (b) pseudo-wave amplitude matrices.
Each matrix has dimensions (f, port_out, port_in) representing
the pseudo-wave amplitudes at each output port due to excitation at each input port.
"""
return self.compute_port_wave_amplitude_matrices(s_param_def="pseudo")
@cached_property
def port_power_wave_matrices(self) -> tuple[TerminalPortDataArray, TerminalPortDataArray]:
"""Compute power-wave amplitude matrices for all port combinations.
This method returns the incident (a) and reflected (b) power-wave amplitude matrices
computed using the power-wave definition from Pozar [2]. The matrices represent the
forward and backward traveling wave amplitudes at each output port when each input
port is excited individually.
Returns
-------
tuple[:class:`.TerminalPortDataArray`, :class:`.TerminalPortDataArray`]
A tuple containing the incident (a) and reflected (b) power-wave amplitude matrices.
Each matrix has dimensions (f, port_out, port_in) representing
the power-wave amplitudes at each output port due to excitation at each input port.
"""
return self.compute_port_wave_amplitude_matrices(s_param_def="power")
@cached_property
def port_symmetric_pseudo_wave_matrices(
self,
) -> tuple[TerminalPortDataArray, TerminalPortDataArray]:
"""Compute symmetric-pseudo wave amplitude matrices for all port combinations.
This method returns the incident (a) and reflected (b) symmetric-pseudo wave amplitude
matrices. These are equivalent to pseudo-waves except for the scaling factor, which
ensures the S-matrix will be symmetric when the simulated device is reciprocal.
Returns
-------
tuple[:class:`.TerminalPortDataArray`, :class:`.TerminalPortDataArray`]
A tuple containing the incident (a) and reflected (b) symmetric-pseudo wave
amplitude matrices. Each matrix has dimensions (f, port_out, port_in) representing
the wave amplitudes at each output port due to excitation at each input port.
"""
return self.compute_port_wave_amplitude_matrices(s_param_def="symmetric_pseudo")
# Mirror Utils
# So they can be reused elsewhere without a class reimport
ab_to_s = staticmethod(ab_to_s)
compute_F = staticmethod(compute_F)
check_port_impedance_sign = staticmethod(check_port_impedance_sign)
compute_port_VI = staticmethod(compute_port_VI)
compute_power_wave_amplitudes = staticmethod(compute_power_wave_amplitudes)
compute_power_delivered_by_port = staticmethod(compute_power_delivered_by_port)