Skip to content

Commit 49f3708

Browse files
Continuing more refactoring
1 parent 8767234 commit 49f3708

File tree

1 file changed

+116
-116
lines changed

1 file changed

+116
-116
lines changed

tidy3d/plugins/smatrix/component_modelers/terminal.py

Lines changed: 116 additions & 116 deletions
Original file line numberDiff line numberDiff line change
@@ -72,7 +72,7 @@ def compute_power_wave_amplitudes(
7272
tuple[FreqDataArray, FreqDataArray]
7373
Incident (a) and reflected (b) power wave amplitude frequency arrays.
7474
"""
75-
voltage, current = TerminalComponentModeler.compute_port_VI(port, sim_data)
75+
voltage, current = compute_port_VI(port, sim_data)
7676
# Amplitudes for the incident and reflected power waves
7777
a = (voltage + port.impedance * current) / 2 / np.sqrt(np.real(port.impedance))
7878
b = (voltage - port.impedance * current) / 2 / np.sqrt(np.real(port.impedance))
@@ -96,11 +96,17 @@ def compute_power_delivered_by_port(
9696
FreqDataArray
9797
Power in units of Watts as a frequency array.
9898
"""
99-
a, b = TerminalComponentModeler.compute_power_wave_amplitudes(sim_data=sim_data, port=port)
99+
a, b = compute_power_wave_amplitudes(sim_data=sim_data, port=port)
100100
# Power delivered is the incident power minus the reflected power
101101
return 0.5 * (np.abs(a) ** 2 - np.abs(b) ** 2)
102102

103103

104+
def _compute_F(Z_numpy: np.array):
105+
"""Helper to convert port impedance matrix to F, which is used for
106+
computing generalized scattering parameters."""
107+
return 1.0 / (2.0 * np.sqrt(np.real(Z_numpy)))
108+
109+
104110
def ab_to_s(
105111
a_matrix: TerminalPortDataArray, b_matrix: TerminalPortDataArray
106112
) -> TerminalPortDataArray:
@@ -146,6 +152,19 @@ def s_to_z(s_matrix: TerminalPortDataArray, reference: Union[complex, PortDataAr
146152
return z_matrix
147153

148154

155+
def _check_port_impedance_sign(Z_numpy: np.ndarray):
156+
"""Sanity check for consistent sign of real part of Z for each port across all frequencies."""
157+
for port_idx in range(Z_numpy.shape[1]):
158+
port_Z = Z_numpy[:, port_idx]
159+
signs = np.sign(np.real(port_Z))
160+
if not np.all(signs == signs[0]):
161+
raise Tidy3dError(
162+
f"Inconsistent sign of real part of Z detected for port {port_idx}. "
163+
"If you received this error, please create an issue in the Tidy3D "
164+
"github repository."
165+
)
166+
167+
149168
class TerminalComponentModeler(AbstractComponentModeler):
150169
"""Tool for modeling two-terminal multiport devices and computing port parameters
151170
with lumped and wave ports."""
@@ -395,105 +414,6 @@ def _check_grid_size_at_wave_ports(simulation: Simulation, ports: list[WavePort]
395414
"for the simulation passed to the 'TerminalComponentModeler'."
396415
)
397416

398-
def compute_power_wave_amplitudes_at_each_port(
399-
self, port_reference_impedances: PortDataArray, sim_data: SimulationData
400-
) -> tuple[PortDataArray, PortDataArray]:
401-
"""Compute the incident and reflected power wave amplitudes at each port.
402-
The computed amplitudes have not been normalized.
403-
404-
Parameters
405-
----------
406-
port_reference_impedances : :class:`.PortDataArray`
407-
Reference impedance at each port.
408-
sim_data : :class:`.SimulationData`
409-
Results from the simulation.
410-
411-
Returns
412-
-------
413-
tuple[:class:`.PortDataArray`, :class:`.PortDataArray`]
414-
Incident (a) and reflected (b) power wave amplitudes at each port.
415-
"""
416-
port_names = [port.name for port in self.ports]
417-
values = np.zeros(
418-
(len(self.freqs), len(port_names)),
419-
dtype=complex,
420-
)
421-
coords = {
422-
"f": np.array(self.freqs),
423-
"port": port_names,
424-
}
425-
426-
V_matrix = PortDataArray(values, coords=coords)
427-
I_matrix = V_matrix.copy(deep=True)
428-
a = V_matrix.copy(deep=True)
429-
b = V_matrix.copy(deep=True)
430-
431-
for port_out in self.ports:
432-
V_out, I_out = self.compute_port_VI(port_out, sim_data)
433-
indexer = {"port": port_out.name}
434-
V_matrix.loc[indexer] = V_out
435-
I_matrix.loc[indexer] = I_out
436-
437-
V_numpy = V_matrix.values
438-
I_numpy = I_matrix.values
439-
Z_numpy = port_reference_impedances.values
440-
441-
# Check to make sure sign is consistent for all impedance values
442-
self._check_port_impedance_sign(Z_numpy)
443-
444-
# # Check for negative real part of port impedance and flip the V and Z signs accordingly
445-
negative_real_Z = np.real(Z_numpy) < 0
446-
V_numpy = np.where(negative_real_Z, -V_numpy, V_numpy)
447-
Z_numpy = np.where(negative_real_Z, -Z_numpy, Z_numpy)
448-
449-
F_numpy = TerminalComponentModeler._compute_F(Z_numpy)
450-
451-
# Equation 4.67 - Pozar - Microwave Engineering 4ed
452-
a.values = F_numpy * (V_numpy + Z_numpy * I_numpy)
453-
b.values = F_numpy * (V_numpy - np.conj(Z_numpy) * I_numpy)
454-
455-
return a, b
456-
457-
@cached_property
458-
def port_reference_impedances(self) -> PortDataArray:
459-
"""The reference impedance used at each port for definining power wave amplitudes."""
460-
return self._port_reference_impedances(self.batch_data)
461-
462-
def _port_reference_impedances(self, batch_data) -> PortDataArray:
463-
"""Tabulates the reference impedance of each port at each frequency using the
464-
supplied :class:`.BatchData`.
465-
"""
466-
# TODO properly refactor, plugins data types should not have web methods.
467-
468-
port_names = [port.name for port in self.ports]
469-
470-
values = np.zeros(
471-
(len(self.freqs), len(port_names)),
472-
dtype=complex,
473-
)
474-
coords = {"f": np.array(self.freqs), "port": port_names}
475-
port_impedances = PortDataArray(values, coords=coords)
476-
for port in self.ports:
477-
if isinstance(port, WavePort):
478-
# Mode solver data for each wave port is stored in its associated SimulationData
479-
sim_data_port = batch_data[self._task_name(port=port)]
480-
# WavePorts have a port impedance calculated from its associated modal field distribution
481-
# and is frequency dependent.
482-
impedances = port.compute_port_impedance(sim_data_port).values
483-
port_impedances.loc[{"port": port.name}] = impedances.squeeze()
484-
else:
485-
# LumpedPorts have a constant reference impedance
486-
port_impedances.loc[{"port": port.name}] = np.full(len(self.freqs), port.impedance)
487-
488-
port_impedances = TerminalComponentModeler._set_port_data_array_attributes(port_impedances)
489-
return port_impedances
490-
491-
@staticmethod
492-
def _compute_F(Z_numpy: np.array):
493-
"""Helper to convert port impedance matrix to F, which is used for
494-
computing generalized scattering parameters."""
495-
return 1.0 / (2.0 * np.sqrt(np.real(Z_numpy)))
496-
497417
@cached_property
498418
def _lumped_ports(self) -> list[AbstractLumpedPort]:
499419
"""A list of all lumped ports in the ``TerminalComponentModeler``"""
@@ -510,19 +430,6 @@ def _set_port_data_array_attributes(data_array: PortDataArray) -> PortDataArray:
510430
data_array.name = "Z0"
511431
return data_array.assign_attrs(units=OHM, long_name="characteristic impedance")
512432

513-
@staticmethod
514-
def _check_port_impedance_sign(Z_numpy: np.ndarray):
515-
"""Sanity check for consistent sign of real part of Z for each port across all frequencies."""
516-
for port_idx in range(Z_numpy.shape[1]):
517-
port_Z = Z_numpy[:, port_idx]
518-
signs = np.sign(np.real(port_Z))
519-
if not np.all(signs == signs[0]):
520-
raise Tidy3dError(
521-
f"Inconsistent sign of real part of Z detected for port {port_idx}. "
522-
"If you received this error, please create an issue in the Tidy3D "
523-
"github repository."
524-
)
525-
526433
def get_radiation_monitor_by_name(self, monitor_name: str) -> DirectivityMonitor:
527434
"""Find and return a :class:`.DirectivityMonitor` monitor by its name.
528435
@@ -546,11 +453,11 @@ def get_radiation_monitor_by_name(self, monitor_name: str) -> DirectivityMonitor
546453
return monitor
547454
raise Tidy3dKeyError(f"No radiation monitor named '{monitor_name}'.")
548455

549-
def run(self, path_dir: str = DEFAULT_DATA_DIR) -> TerminalPortDataArray:
456+
def run(self, path_dir: str = DEFAULT_DATA_DIR) -> TerminalComponentModelerData:
550457
"""Solves for the scattering matrix of the system."""
551458
_ = self.get_path_dir(path_dir)
552459
_ = self._upload_terminal_modeler()
553-
return self._construct_smatrix()
460+
_ = self._construct_smatrix()
554461

555462
@cached_property
556463
def _terminal_modeler_path(self) -> str:
@@ -748,3 +655,96 @@ def get_antenna_metrics_data(
748655
return AntennaMetricsData.from_directivity_data(
749656
combined_directivity_data, power_incident, power_reflected
750657
)
658+
659+
@cached_property
660+
def port_reference_impedances(self) -> PortDataArray:
661+
"""The reference impedance used at each port for definining power wave amplitudes."""
662+
return self._port_reference_impedances(self.batch_data)
663+
664+
def _port_reference_impedances(self, batch_data) -> PortDataArray:
665+
"""Tabulates the reference impedance of each port at each frequency using the
666+
supplied :class:`.BatchData`.
667+
"""
668+
# TODO properly refactor, plugins data types should not have web methods.
669+
670+
port_names = [port.name for port in self.ports]
671+
672+
values = np.zeros(
673+
(len(self.freqs), len(port_names)),
674+
dtype=complex,
675+
)
676+
coords = {"f": np.array(self.freqs), "port": port_names}
677+
port_impedances = PortDataArray(values, coords=coords)
678+
for port in self.ports:
679+
if isinstance(port, WavePort):
680+
# Mode solver data for each wave port is stored in its associated SimulationData
681+
sim_data_port = batch_data[self._task_name(port=port)]
682+
# WavePorts have a port impedance calculated from its associated modal field distribution
683+
# and is frequency dependent.
684+
impedances = port.compute_port_impedance(sim_data_port).values
685+
port_impedances.loc[{"port": port.name}] = impedances.squeeze()
686+
else:
687+
# LumpedPorts have a constant reference impedance
688+
port_impedances.loc[{"port": port.name}] = np.full(len(self.freqs), port.impedance)
689+
690+
port_impedances = TerminalComponentModeler._set_port_data_array_attributes(port_impedances)
691+
return port_impedances
692+
693+
def compute_power_wave_amplitudes_at_each_port(
694+
self, port_reference_impedances: PortDataArray, sim_data: SimulationData
695+
) -> tuple[PortDataArray, PortDataArray]:
696+
"""Compute the incident and reflected power wave amplitudes at each port.
697+
The computed amplitudes have not been normalized.
698+
699+
Parameters
700+
----------
701+
port_reference_impedances : :class:`.PortDataArray`
702+
Reference impedance at each port.
703+
sim_data : :class:`.SimulationData`
704+
Results from the simulation.
705+
706+
Returns
707+
-------
708+
tuple[:class:`.PortDataArray`, :class:`.PortDataArray`]
709+
Incident (a) and reflected (b) power wave amplitudes at each port.
710+
"""
711+
port_names = [port.name for port in self.ports]
712+
values = np.zeros(
713+
(len(self.freqs), len(port_names)),
714+
dtype=complex,
715+
)
716+
coords = {
717+
"f": np.array(self.freqs),
718+
"port": port_names,
719+
}
720+
721+
V_matrix = PortDataArray(values, coords=coords)
722+
I_matrix = V_matrix.copy(deep=True)
723+
a = V_matrix.copy(deep=True)
724+
b = V_matrix.copy(deep=True)
725+
726+
for port_out in self.ports:
727+
V_out, I_out = compute_port_VI(port_out, sim_data)
728+
indexer = {"port": port_out.name}
729+
V_matrix.loc[indexer] = V_out
730+
I_matrix.loc[indexer] = I_out
731+
732+
V_numpy = V_matrix.values
733+
I_numpy = I_matrix.values
734+
Z_numpy = port_reference_impedances.values
735+
736+
# Check to make sure sign is consistent for all impedance values
737+
_check_port_impedance_sign(Z_numpy)
738+
739+
# # Check for negative real part of port impedance and flip the V and Z signs accordingly
740+
negative_real_Z = np.real(Z_numpy) < 0
741+
V_numpy = np.where(negative_real_Z, -V_numpy, V_numpy)
742+
Z_numpy = np.where(negative_real_Z, -Z_numpy, Z_numpy)
743+
744+
F_numpy = TerminalComponentModeler._compute_F(Z_numpy)
745+
746+
# Equation 4.67 - Pozar - Microwave Engineering 4ed
747+
a.values = F_numpy * (V_numpy + Z_numpy * I_numpy)
748+
b.values = F_numpy * (V_numpy - np.conj(Z_numpy) * I_numpy)
749+
750+
return a, b

0 commit comments

Comments
 (0)