From 458def1f7b359bf2278a12378d2c43b42a7d870f Mon Sep 17 00:00:00 2001 From: dmarek Date: Mon, 4 Aug 2025 16:18:38 -0400 Subject: [PATCH 1/2] immutable data arrays for rf fix setting data attributes --- tidy3d/components/data/data_array.py | 182 ++++++++++++++++++ .../microwave/custom_path_integrals.py | 32 +-- .../plugins/microwave/impedance_calculator.py | 25 +-- tidy3d/plugins/microwave/path_integrals.py | 55 ++---- tidy3d/plugins/smatrix/utils.py | 74 ++++++- 5 files changed, 298 insertions(+), 70 deletions(-) diff --git a/tidy3d/components/data/data_array.py b/tidy3d/components/data/data_array.py index 5b03ca0afb..5923417317 100644 --- a/tidy3d/components/data/data_array.py +++ b/tidy3d/components/data/data_array.py @@ -23,11 +23,14 @@ from tidy3d.components.geometry.bound_ops import bounds_contains from tidy3d.components.types import Axis, Bound from tidy3d.constants import ( + AMP, HERTZ, MICROMETER, + OHM, PICOSECOND_PER_NANOMETER_PER_KILOMETER, RADIAN, SECOND, + VOLT, WATT, ) from tidy3d.exceptions import DataError, FileError @@ -1338,6 +1341,165 @@ class PerturbationCoefficientDataArray(DataArray): _dims = ("wvl", "coeff") +class VoltageArray(DataArray): + # Always set __slots__ = () to avoid xarray warnings + __slots__ = () + _data_attrs = {"units": VOLT, "long_name": "voltage"} + + +class CurrentArray(DataArray): + # Always set __slots__ = () to avoid xarray warnings + __slots__ = () + _data_attrs = {"units": AMP, "long_name": "current"} + + +class ImpedanceArray(DataArray): + # Always set __slots__ = () to avoid xarray warnings + __slots__ = () + _data_attrs = {"units": OHM, "long_name": "impedance"} + + +# Voltage arrays +class VoltageFreqDataArray(VoltageArray, FreqDataArray): + """Voltage data array in frequency domain. + + Example + ------- + >>> import numpy as np + >>> f = [2e9, 3e9, 4e9] + >>> coords = dict(f=f) + >>> data = np.random.random(3) + 1j * np.random.random(3) + >>> vfd = VoltageFreqDataArray(data, coords=coords) + """ + + __slots__ = () + + +class VoltageTimeDataArray(VoltageArray, TimeDataArray): + """Voltage data array in time domain. + + Example + ------- + >>> import numpy as np + >>> t = [0, 1e-9, 2e-9, 3e-9] + >>> coords = dict(t=t) + >>> data = np.sin(2 * np.pi * 1e9 * np.array(t)) + >>> vtd = VoltageTimeDataArray(data, coords=coords) + """ + + __slots__ = () + + +class VoltageFreqModeDataArray(VoltageArray, FreqModeDataArray): + """Voltage data array in frequency-mode domain. + + Example + ------- + >>> import numpy as np + >>> f = [2e9, 3e9] + >>> mode_index = [0, 1] + >>> coords = dict(f=f, mode_index=mode_index) + >>> data = np.random.random((2, 2)) + 1j * np.random.random((2, 2)) + >>> vfmd = VoltageFreqModeDataArray(data, coords=coords) + """ + + __slots__ = () + + +# Current arrays +class CurrentFreqDataArray(CurrentArray, FreqDataArray): + """Current data array in frequency domain. + + Example + ------- + >>> import numpy as np + >>> f = [2e9, 3e9, 4e9] + >>> coords = dict(f=f) + >>> data = np.random.random(3) + 1j * np.random.random(3) + >>> cfd = CurrentFreqDataArray(data, coords=coords) + """ + + __slots__ = () + + +class CurrentTimeDataArray(CurrentArray, TimeDataArray): + """Current data array in time domain. + + Example + ------- + >>> import numpy as np + >>> t = [0, 1e-9, 2e-9, 3e-9] + >>> coords = dict(t=t) + >>> data = np.cos(2 * np.pi * 1e9 * np.array(t)) + >>> ctd = CurrentTimeDataArray(data, coords=coords) + """ + + __slots__ = () + + +class CurrentFreqModeDataArray(CurrentArray, FreqModeDataArray): + """Current data array in frequency-mode domain. + + Example + ------- + >>> import numpy as np + >>> f = [2e9, 3e9] + >>> mode_index = [0, 1] + >>> coords = dict(f=f, mode_index=mode_index) + >>> data = np.random.random((2, 2)) + 1j * np.random.random((2, 2)) + >>> cfmd = CurrentFreqModeDataArray(data, coords=coords) + """ + + __slots__ = () + + +# Impedance arrays +class ImpedanceFreqDataArray(ImpedanceArray, FreqDataArray): + """Impedance data array in frequency domain. + + Example + ------- + >>> import numpy as np + >>> f = [2e9, 3e9, 4e9] + >>> coords = dict(f=f) + >>> data = 50.0 + 1j * np.random.random(3) + >>> zfd = ImpedanceFreqDataArray(data, coords=coords) + """ + + __slots__ = () + + +class ImpedanceTimeDataArray(ImpedanceArray, TimeDataArray): + """Impedance data array in time domain. + + Example + ------- + >>> import numpy as np + >>> t = [0, 1e-9, 2e-9, 3e-9] + >>> coords = dict(t=t) + >>> data = 50.0 * np.ones_like(t) + >>> ztd = ImpedanceTimeDataArray(data, coords=coords) + """ + + __slots__ = () + + +class ImpedanceFreqModeDataArray(ImpedanceArray, FreqModeDataArray): + """Impedance data array in frequency-mode domain. + + Example + ------- + >>> import numpy as np + >>> f = [2e9, 3e9] + >>> mode_index = [0, 1] + >>> coords = dict(f=f, mode_index=mode_index) + >>> data = 50.0 + 10.0 * np.random.random((2, 2)) + >>> zfmd = ImpedanceFreqModeDataArray(data, coords=coords) + """ + + __slots__ = () + + DATA_ARRAY_TYPES = [ SpatialDataArray, ScalarFieldDataArray, @@ -1375,6 +1537,15 @@ class PerturbationCoefficientDataArray(DataArray): SpatialVoltageDataArray, PerturbationCoefficientDataArray, IndexedTimeDataArray, + VoltageFreqDataArray, + VoltageTimeDataArray, + VoltageFreqModeDataArray, + CurrentFreqDataArray, + CurrentTimeDataArray, + CurrentFreqModeDataArray, + ImpedanceFreqDataArray, + ImpedanceTimeDataArray, + ImpedanceFreqModeDataArray, ] DATA_ARRAY_MAP = {data_array.__name__: data_array for data_array in DATA_ARRAY_TYPES} @@ -1385,3 +1556,14 @@ class PerturbationCoefficientDataArray(DataArray): IndexedFieldVoltageDataArray, PointDataArray, ] + +IntegralResultTypes = Union[FreqDataArray, FreqModeDataArray, TimeDataArray] +VoltageIntegralResultTypes = Union[ + VoltageFreqDataArray, VoltageFreqModeDataArray, VoltageTimeDataArray +] +CurrentIntegralResultTypes = Union[ + CurrentFreqDataArray, CurrentFreqModeDataArray, CurrentTimeDataArray +] +ImpedanceResultTypes = Union[ + ImpedanceFreqDataArray, ImpedanceFreqModeDataArray, ImpedanceTimeDataArray +] diff --git a/tidy3d/plugins/microwave/custom_path_integrals.py b/tidy3d/plugins/microwave/custom_path_integrals.py index d1e2c89a19..311ec819d8 100644 --- a/tidy3d/plugins/microwave/custom_path_integrals.py +++ b/tidy3d/plugins/microwave/custom_path_integrals.py @@ -19,10 +19,10 @@ from .path_integrals import ( AbstractAxesRH, AxisAlignedPathIntegral, - CurrentIntegralAxisAligned, + CurrentIntegralResultTypes, IntegralResultTypes, MonitorDataTypes, - VoltageIntegralAxisAligned, + VoltageIntegralResultTypes, ) from .viz import ( ARROW_CURRENT, @@ -89,6 +89,10 @@ def compute_integral( Result of integral over remaining dimensions (frequency, time, mode indices). """ + from tidy3d.plugins.smatrix.utils import ( + _make_base_result_data_array, + ) + (dim1, dim2, dim3) = self.local_dims h_field_name = f"{field}{dim1}" @@ -130,7 +134,7 @@ def compute_integral( # Integrate along the path result = integrand.integrate(coord="s") result = result.reset_coords(drop=True) - return AxisAlignedPathIntegral._make_result_data_array(result) + return _make_base_result_data_array(result) @staticmethod def _compute_dl_component(coord_array: xr.DataArray, closed_contour=False) -> np.array: @@ -243,7 +247,7 @@ class CustomVoltageIntegral2D(CustomPathIntegral2D): .. TODO Improve by including extrapolate_to_endpoints field, non-trivial extension.""" - def compute_voltage(self, em_field: MonitorDataTypes) -> IntegralResultTypes: + def compute_voltage(self, em_field: MonitorDataTypes) -> VoltageIntegralResultTypes: """Compute voltage along path defined by a line. Parameters @@ -253,13 +257,16 @@ def compute_voltage(self, em_field: MonitorDataTypes) -> IntegralResultTypes: Returns ------- - :class:`.IntegralResultTypes` + :class:`.VoltageIntegralResultTypes` Result of voltage computation over remaining dimensions (frequency, time, mode indices). """ + from tidy3d.plugins.smatrix.utils import ( + _make_voltage_data_array, + ) + AxisAlignedPathIntegral._check_monitor_data_supported(em_field=em_field) voltage = -1.0 * self.compute_integral(field="E", em_field=em_field) - voltage = VoltageIntegralAxisAligned._set_data_array_attributes(voltage) - return voltage + return _make_voltage_data_array(voltage) @add_ax_if_none def plot( @@ -316,7 +323,7 @@ class CustomCurrentIntegral2D(CustomPathIntegral2D): To compute the current flowing in the positive ``axis`` direction, the vertices should be ordered in a counterclockwise direction.""" - def compute_current(self, em_field: MonitorDataTypes) -> IntegralResultTypes: + def compute_current(self, em_field: MonitorDataTypes) -> CurrentIntegralResultTypes: """Compute current flowing in a custom loop. Parameters @@ -326,13 +333,16 @@ def compute_current(self, em_field: MonitorDataTypes) -> IntegralResultTypes: Returns ------- - :class:`.IntegralResultTypes` + :class:`.CurrentIntegralResultTypes` Result of current computation over remaining dimensions (frequency, time, mode indices). """ + from tidy3d.plugins.smatrix.utils import ( + _make_current_data_array, + ) + AxisAlignedPathIntegral._check_monitor_data_supported(em_field=em_field) current = self.compute_integral(field="H", em_field=em_field) - current = CurrentIntegralAxisAligned._set_data_array_attributes(current) - return current + return _make_current_data_array(current) @add_ax_if_none def plot( diff --git a/tidy3d/plugins/microwave/impedance_calculator.py b/tidy3d/plugins/microwave/impedance_calculator.py index a400818620..df9c729580 100644 --- a/tidy3d/plugins/microwave/impedance_calculator.py +++ b/tidy3d/plugins/microwave/impedance_calculator.py @@ -8,10 +8,9 @@ import pydantic.v1 as pd from tidy3d.components.base import Tidy3dBaseModel -from tidy3d.components.data.data_array import FreqDataArray, FreqModeDataArray, TimeDataArray +from tidy3d.components.data.data_array import ImpedanceResultTypes from tidy3d.components.data.monitor_data import FieldTimeData from tidy3d.components.monitor import ModeMonitor, ModeSolverMonitor -from tidy3d.constants import OHM from tidy3d.exceptions import ValidationError from tidy3d.log import log @@ -19,7 +18,6 @@ from .path_integrals import ( AxisAlignedPathIntegral, CurrentIntegralAxisAligned, - IntegralResultTypes, MonitorDataTypes, VoltageIntegralAxisAligned, ) @@ -43,7 +41,7 @@ class ImpedanceCalculator(Tidy3dBaseModel): description="Definition of contour integral for computing current.", ) - def compute_impedance(self, em_field: MonitorDataTypes) -> IntegralResultTypes: + def compute_impedance(self, em_field: MonitorDataTypes) -> ImpedanceResultTypes: """Compute impedance for the supplied ``em_field`` using ``voltage_integral`` and ``current_integral``. If only a single integral has been defined, impedance is computed using the total flux in ``em_field``. @@ -56,9 +54,11 @@ def compute_impedance(self, em_field: MonitorDataTypes) -> IntegralResultTypes: Returns ------- - :class:`.IntegralResultTypes` + :class:`.ImpedanceResultTypes` Result of impedance computation over remaining dimensions (frequency, time, mode indices). """ + from tidy3d.plugins.smatrix.utils import _make_impedance_data_array + AxisAlignedPathIntegral._check_monitor_data_supported(em_field=em_field) # If both voltage and current integrals have been defined then impedance is computed directly @@ -98,7 +98,7 @@ def compute_impedance(self, em_field: MonitorDataTypes) -> IntegralResultTypes: impedance = np.real(voltage) / np.real(current) else: impedance = voltage / current - impedance = ImpedanceCalculator._set_data_array_attributes(impedance) + impedance = _make_impedance_data_array(impedance) return impedance @pd.validator("current_integral", always=True) @@ -111,19 +111,6 @@ def check_voltage_or_current(cls, val, values): ) return val - @staticmethod - def _set_data_array_attributes(data_array: IntegralResultTypes) -> IntegralResultTypes: - """Helper to set additional metadata for ``IntegralResultTypes``.""" - # Determine type based on coords present - if "mode_index" in data_array.coords: - data_array = FreqModeDataArray(data_array) - elif "f" in data_array.coords: - data_array = FreqDataArray(data_array) - else: - data_array = TimeDataArray(data_array) - data_array.name = "Z0" - return data_array.assign_attrs(units=OHM, long_name="characteristic impedance") - @pd.root_validator(pre=False) def _warn_rf_license(cls, values): log.warning( diff --git a/tidy3d/plugins/microwave/path_integrals.py b/tidy3d/plugins/microwave/path_integrals.py index 802f5b0d92..75d1f893de 100644 --- a/tidy3d/plugins/microwave/path_integrals.py +++ b/tidy3d/plugins/microwave/path_integrals.py @@ -7,23 +7,22 @@ import numpy as np import pydantic.v1 as pd -import xarray as xr from tidy3d.components.base import Tidy3dBaseModel, cached_property from tidy3d.components.data.data_array import ( - FreqDataArray, - FreqModeDataArray, + CurrentIntegralResultTypes, + IntegralResultTypes, ScalarFieldDataArray, ScalarFieldTimeDataArray, ScalarModeFieldDataArray, - TimeDataArray, + VoltageIntegralResultTypes, ) from tidy3d.components.data.monitor_data import FieldData, FieldTimeData, ModeData, ModeSolverData from tidy3d.components.geometry.base import Box, Geometry from tidy3d.components.types import Ax, Axis, Coordinate2D, Direction from tidy3d.components.validators import assert_line, assert_plane from tidy3d.components.viz import add_ax_if_none -from tidy3d.constants import AMP, VOLT, fp_eps +from tidy3d.constants import fp_eps from tidy3d.exceptions import DataError, Tidy3dError from tidy3d.log import log @@ -37,7 +36,6 @@ MonitorDataTypes = Union[FieldData, FieldTimeData, ModeData, ModeSolverData] EMScalarFieldType = Union[ScalarFieldDataArray, ScalarFieldTimeDataArray, ScalarModeFieldDataArray] -IntegralResultTypes = Union[FreqDataArray, FreqModeDataArray, TimeDataArray] class AbstractAxesRH(Tidy3dBaseModel, ABC): @@ -103,6 +101,9 @@ class AxisAlignedPathIntegral(AbstractAxesRH, Box): def compute_integral(self, scalar_field: EMScalarFieldType) -> IntegralResultTypes: """Computes the defined integral given the input ``scalar_field``.""" + from tidy3d.plugins.smatrix.utils import ( + _make_base_result_data_array, + ) if not scalar_field.does_cover(self.bounds, fp_eps, np.finfo(np.float32).smallest_normal): raise DataError("Scalar field does not cover the integration domain.") @@ -137,7 +138,7 @@ def compute_integral(self, scalar_field: EMScalarFieldType) -> IntegralResultTyp coords_interp, method=method, kwargs={"fill_value": "extrapolate"} ) result = scalar_field.integrate(coord=coord) - return self._make_result_data_array(result) + return _make_base_result_data_array(result) def _get_field_along_path(self, scalar_field: EMScalarFieldType) -> EMScalarFieldType: """Returns a selection of the input ``scalar_field`` ready for integration.""" @@ -205,15 +206,6 @@ def _check_monitor_data_supported(em_field: MonitorDataTypes): f"{supported_types}" ) - @staticmethod - def _make_result_data_array(result: xr.DataArray) -> IntegralResultTypes: - """Helper for creating the proper result type.""" - if "t" in result.coords: - return TimeDataArray(data=result.data, coords=result.coords) - if "f" in result.coords and "mode_index" in result.coords: - return FreqModeDataArray(data=result.data, coords=result.coords) - return FreqDataArray(data=result.data, coords=result.coords) - class VoltageIntegralAxisAligned(AxisAlignedPathIntegral): """Class for computing the voltage between two points defined by an axis-aligned line.""" @@ -224,8 +216,12 @@ class VoltageIntegralAxisAligned(AxisAlignedPathIntegral): description="Positive indicates V=Vb-Va where position b has a larger coordinate along the axis of integration.", ) - def compute_voltage(self, em_field: MonitorDataTypes) -> IntegralResultTypes: + def compute_voltage(self, em_field: MonitorDataTypes) -> VoltageIntegralResultTypes: """Compute voltage along path defined by a line.""" + from tidy3d.plugins.smatrix.utils import ( + _make_voltage_data_array, + ) + self._check_monitor_data_supported(em_field=em_field) e_component = "xyz"[self.main_axis] field_name = f"E{e_component}" @@ -238,15 +234,7 @@ def compute_voltage(self, em_field: MonitorDataTypes) -> IntegralResultTypes: if self.sign == "+": voltage *= -1 - voltage = VoltageIntegralAxisAligned._set_data_array_attributes(voltage) - # Return data array of voltage while keeping coordinates of frequency|time|mode index - return voltage - - @staticmethod - def _set_data_array_attributes(data_array: IntegralResultTypes) -> IntegralResultTypes: - """Add explanatory attributes to the data array.""" - data_array.name = "V" - return data_array.assign_attrs(units=VOLT, long_name="voltage") + return _make_voltage_data_array(voltage) @staticmethod def from_terminal_positions( @@ -381,8 +369,12 @@ class CurrentIntegralAxisAligned(AbstractAxesRH, Box): description="This parameter is passed to :class:`AxisAlignedPathIntegral` objects when computing the contour integral.", ) - def compute_current(self, em_field: MonitorDataTypes) -> IntegralResultTypes: + def compute_current(self, em_field: MonitorDataTypes) -> CurrentIntegralResultTypes: """Compute current flowing in loop defined by the outer edge of a rectangle.""" + from tidy3d.plugins.smatrix.utils import ( + _make_current_data_array, + ) + AxisAlignedPathIntegral._check_monitor_data_supported(em_field=em_field) ax1 = self.remaining_axes[0] ax2 = self.remaining_axes[1] @@ -407,8 +399,7 @@ def compute_current(self, em_field: MonitorDataTypes) -> IntegralResultTypes: if self.sign == "-": current *= -1 - current = CurrentIntegralAxisAligned._set_data_array_attributes(current) - return current + return _make_current_data_array(current) @cached_property def main_axis(self) -> Axis: @@ -498,12 +489,6 @@ def _to_path_integrals( return (bottom, right, top, left) - @staticmethod - def _set_data_array_attributes(data_array: IntegralResultTypes) -> IntegralResultTypes: - """Add explanatory attributes to the data array.""" - data_array.name = "I" - return data_array.assign_attrs(units=AMP, long_name="current") - @add_ax_if_none def plot( self, diff --git a/tidy3d/plugins/smatrix/utils.py b/tidy3d/plugins/smatrix/utils.py index 2f493701ed..65c7d2902c 100644 --- a/tidy3d/plugins/smatrix/utils.py +++ b/tidy3d/plugins/smatrix/utils.py @@ -4,7 +4,25 @@ import numpy as np -from tidy3d.components.data.data_array import DataArray, FreqDataArray +from tidy3d.components.data.data_array import ( + CurrentFreqDataArray, + CurrentFreqModeDataArray, + CurrentIntegralResultTypes, + CurrentTimeDataArray, + DataArray, + FreqDataArray, + FreqModeDataArray, + ImpedanceFreqDataArray, + ImpedanceFreqModeDataArray, + ImpedanceResultTypes, + ImpedanceTimeDataArray, + IntegralResultTypes, + TimeDataArray, + VoltageFreqDataArray, + VoltageFreqModeDataArray, + VoltageIntegralResultTypes, + VoltageTimeDataArray, +) from tidy3d.components.data.sim_data import SimulationData from tidy3d.components.types import ArrayFloat1D from tidy3d.exceptions import Tidy3dError @@ -16,6 +34,13 @@ from tidy3d.plugins.smatrix.network import SParamDef from tidy3d.plugins.smatrix.ports.coaxial_lumped import CoaxialLumpedPort from tidy3d.plugins.smatrix.ports.rectangular_lumped import LumpedPort +from tidy3d.plugins.smatrix.data.data_array import PortDataArray, TerminalPortDataArray +from tidy3d.plugins.smatrix.ports.types import LumpedPortType, TerminalPortType + + +def port_array_inv(matrix: DataArray): + """Helper to invert a port matrix.""" + return np.linalg.inv(matrix) def ab_to_s( @@ -114,7 +139,7 @@ def compute_port_VI( def compute_power_wave_amplitudes( - port: Union[LumpedPort, CoaxialLumpedPort], sim_data: SimulationData + port: LumpedPortType, sim_data: SimulationData ) -> tuple[FreqDataArray, FreqDataArray]: """Calculates the unnormalized power wave amplitudes from port voltage (V), current (I), and impedance (Z0) using: @@ -125,7 +150,7 @@ def compute_power_wave_amplitudes( Parameters ---------- - port : Union[:class:`.LumpedPort`, :class:`.CoaxialLumpedPort`] + port : :class:`.LumpedPortType` Port for computing voltage and current. sim_data : :class:`.SimulationData` Results from the simulation. @@ -143,7 +168,7 @@ def compute_power_wave_amplitudes( def compute_power_delivered_by_port( - port: Union[LumpedPort, CoaxialLumpedPort], sim_data: SimulationData + port: LumpedPortType, sim_data: SimulationData ) -> FreqDataArray: """Compute the power delivered to the network by a lumped port. @@ -152,7 +177,7 @@ def compute_power_delivered_by_port( Parameters ---------- - port : Union[:class:`.LumpedPort`, :class:`.CoaxialLumpedPort`] + port : :class:`.LumpedPortType` Port for computing voltage and current. sim_data : :class:`.SimulationData` Results from the simulation. @@ -246,3 +271,42 @@ def validate_square_matrix(matrix: TerminalPortDataArray, method_name: str) -> N "was run with only a subset of port excitations. Please ensure that the `run_only` field in " "the 'TerminalComponentModeler' is not being used." ) + +def _make_base_result_data_array(result: DataArray) -> IntegralResultTypes: + """Helper for creating the proper base result type.""" + cls = FreqDataArray + if "t" in result.coords: + cls = TimeDataArray + if "f" in result.coords and "mode_index" in result.coords: + cls = FreqModeDataArray + return cls.assign_data_attrs(cls(data=result.data, coords=result.coords)) + + +def _make_voltage_data_array(result: DataArray) -> CurrentIntegralResultTypes: + """Helper for creating the proper voltage array type.""" + cls = CurrentFreqDataArray + if "t" in result.coords: + cls = CurrentTimeDataArray + if "f" in result.coords and "mode_index" in result.coords: + cls = CurrentFreqModeDataArray + return cls.assign_data_attrs(cls(data=result.data, coords=result.coords)) + + +def _make_current_data_array(result: DataArray) -> VoltageIntegralResultTypes: + """Helper for creating the proper current array type.""" + cls = VoltageFreqDataArray + if "t" in result.coords: + cls = VoltageTimeDataArray + if "f" in result.coords and "mode_index" in result.coords: + cls = VoltageFreqModeDataArray + return cls.assign_data_attrs(cls(data=result.data, coords=result.coords)) + + +def _make_impedance_data_array(result: DataArray) -> ImpedanceResultTypes: + """Helper for creating the proper impedance array type.""" + cls = ImpedanceFreqDataArray + if "t" in result.coords: + cls = ImpedanceTimeDataArray + if "f" in result.coords and "mode_index" in result.coords: + cls = ImpedanceFreqModeDataArray + return cls.assign_data_attrs(cls(data=result.data, coords=result.coords)) From dccf437adc53911d3a4cf439d12e22c8524a15e7 Mon Sep 17 00:00:00 2001 From: dmarek Date: Thu, 7 Aug 2025 16:05:51 -0400 Subject: [PATCH 2/2] fix monkeypatch and anticipate some changes --- .../smatrix/test_terminal_component_modeler.py | 5 +++-- tidy3d/plugins/smatrix/data/terminal.py | 11 ++++++++++- 2 files changed, 13 insertions(+), 3 deletions(-) diff --git a/tests/test_plugins/smatrix/test_terminal_component_modeler.py b/tests/test_plugins/smatrix/test_terminal_component_modeler.py index b744fd437c..198f3e84e3 100644 --- a/tests/test_plugins/smatrix/test_terminal_component_modeler.py +++ b/tests/test_plugins/smatrix/test_terminal_component_modeler.py @@ -20,7 +20,6 @@ VoltageIntegralAxisAligned, ) from tidy3d.plugins.smatrix import ( - AbstractComponentModeler, CoaxialLumpedPort, LumpedPort, PortDataArray, @@ -48,7 +47,9 @@ def run_component_modeler( values=tuple(batch_data.values()), ) modeler_data = TerminalComponentModelerData(modeler=modeler, data=port_data) - monkeypatch.setattr(AbstractComponentModeler, "inv", lambda matrix: np.eye(len(modeler.ports))) + monkeypatch.setattr( + td.plugins.smatrix.utils, "port_array_inv", lambda matrix: np.eye(len(modeler.ports)) + ) monkeypatch.setattr( td.plugins.smatrix.utils, "compute_F", diff --git a/tidy3d/plugins/smatrix/data/terminal.py b/tidy3d/plugins/smatrix/data/terminal.py index 53953e0dc8..622dcec52b 100644 --- a/tidy3d/plugins/smatrix/data/terminal.py +++ b/tidy3d/plugins/smatrix/data/terminal.py @@ -2,7 +2,7 @@ from __future__ import annotations -from typing import Optional, Union +from typing import Literal, Optional, Union import numpy as np import pydantic.v1 as pd @@ -28,6 +28,9 @@ s_to_z, ) +# The definition of wave amplitudes used to construct scattering matrix +SParamDef = Literal["pseudo", "power"] + class MicrowaveSMatrixData(Tidy3dBaseModel): """Stores the computed S-matrix and reference impedances for the terminal ports.""" @@ -44,6 +47,12 @@ class MicrowaveSMatrixData(Tidy3dBaseModel): 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 = pd.Field( + "pseudo", + title="Scattering Parameter Definition", + description="Whether to scattering parameters are defined using the 'pseudo' or 'power' wave definitions.", + ) + class TerminalComponentModelerData(Tidy3dBaseModel): """