@@ -72,7 +72,7 @@ def compute_power_wave_amplitudes(
72
72
tuple[FreqDataArray, FreqDataArray]
73
73
Incident (a) and reflected (b) power wave amplitude frequency arrays.
74
74
"""
75
- voltage , current = TerminalComponentModeler . compute_port_VI (port , sim_data )
75
+ voltage , current = compute_port_VI (port , sim_data )
76
76
# Amplitudes for the incident and reflected power waves
77
77
a = (voltage + port .impedance * current ) / 2 / np .sqrt (np .real (port .impedance ))
78
78
b = (voltage - port .impedance * current ) / 2 / np .sqrt (np .real (port .impedance ))
@@ -96,11 +96,17 @@ def compute_power_delivered_by_port(
96
96
FreqDataArray
97
97
Power in units of Watts as a frequency array.
98
98
"""
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 )
100
100
# Power delivered is the incident power minus the reflected power
101
101
return 0.5 * (np .abs (a ) ** 2 - np .abs (b ) ** 2 )
102
102
103
103
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
+
104
110
def ab_to_s (
105
111
a_matrix : TerminalPortDataArray , b_matrix : TerminalPortDataArray
106
112
) -> TerminalPortDataArray :
@@ -146,6 +152,19 @@ def s_to_z(s_matrix: TerminalPortDataArray, reference: Union[complex, PortDataAr
146
152
return z_matrix
147
153
148
154
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
+
149
168
class TerminalComponentModeler (AbstractComponentModeler ):
150
169
"""Tool for modeling two-terminal multiport devices and computing port parameters
151
170
with lumped and wave ports."""
@@ -395,105 +414,6 @@ def _check_grid_size_at_wave_ports(simulation: Simulation, ports: list[WavePort]
395
414
"for the simulation passed to the 'TerminalComponentModeler'."
396
415
)
397
416
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
-
497
417
@cached_property
498
418
def _lumped_ports (self ) -> list [AbstractLumpedPort ]:
499
419
"""A list of all lumped ports in the ``TerminalComponentModeler``"""
@@ -510,19 +430,6 @@ def _set_port_data_array_attributes(data_array: PortDataArray) -> PortDataArray:
510
430
data_array .name = "Z0"
511
431
return data_array .assign_attrs (units = OHM , long_name = "characteristic impedance" )
512
432
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
-
526
433
def get_radiation_monitor_by_name (self , monitor_name : str ) -> DirectivityMonitor :
527
434
"""Find and return a :class:`.DirectivityMonitor` monitor by its name.
528
435
@@ -546,11 +453,11 @@ def get_radiation_monitor_by_name(self, monitor_name: str) -> DirectivityMonitor
546
453
return monitor
547
454
raise Tidy3dKeyError (f"No radiation monitor named '{ monitor_name } '." )
548
455
549
- def run (self , path_dir : str = DEFAULT_DATA_DIR ) -> TerminalPortDataArray :
456
+ def run (self , path_dir : str = DEFAULT_DATA_DIR ) -> TerminalComponentModelerData :
550
457
"""Solves for the scattering matrix of the system."""
551
458
_ = self .get_path_dir (path_dir )
552
459
_ = self ._upload_terminal_modeler ()
553
- return self ._construct_smatrix ()
460
+ _ = self ._construct_smatrix ()
554
461
555
462
@cached_property
556
463
def _terminal_modeler_path (self ) -> str :
@@ -748,3 +655,96 @@ def get_antenna_metrics_data(
748
655
return AntennaMetricsData .from_directivity_data (
749
656
combined_directivity_data , power_incident , power_reflected
750
657
)
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