2
2
3
3
from __future__ import annotations
4
4
5
- from typing import Optional , Union
5
+ from typing import Literal , Optional , Union
6
6
7
7
import numpy as np
8
8
import pydantic .v1 as pd
33
33
34
34
class TerminalComponentModeler (AbstractComponentModeler ):
35
35
"""Tool for modeling two-terminal multiport devices and computing port parameters
36
- with lumped and wave ports."""
36
+ with lumped and wave ports.
37
+
38
+ Notes
39
+ -----
40
+ **References**
41
+
42
+ [1] R. B. Marks and D. F. Williams, "A general waveguide circuit theory,"
43
+ J. Res. Natl. Inst. Stand. Technol., vol. 97, pp. 533, 1992.
44
+
45
+ [2] D. M. Pozar, Microwave Engineering, 4th ed. Hoboken, NJ, USA: John Wiley & Sons, 2012.
46
+ """
37
47
38
48
ports : tuple [TerminalPortType , ...] = pd .Field (
39
49
(),
@@ -220,7 +230,9 @@ def _internal_construct_smatrix(self, batch_data: BatchData) -> TerminalPortData
220
230
# loop through source ports
221
231
for port_in in self .ports :
222
232
sim_data = batch_data [self ._task_name (port = port_in )]
223
- a , b = self .compute_power_wave_amplitudes_at_each_port (port_impedances , sim_data )
233
+ a , b = self .compute_wave_amplitudes_at_each_port (
234
+ port_impedances , sim_data , wave_type = "pseudo"
235
+ )
224
236
indexer = {"f" : a .f , "port_in" : port_in .name , "port_out" : a .port }
225
237
a_matrix .loc [indexer ] = a
226
238
b_matrix .loc [indexer ] = b
@@ -280,10 +292,13 @@ def _check_grid_size_at_wave_ports(simulation: Simulation, ports: list[WavePort]
280
292
"for the simulation passed to the 'TerminalComponentModeler'."
281
293
)
282
294
283
- def compute_power_wave_amplitudes_at_each_port (
284
- self , port_reference_impedances : PortDataArray , sim_data : SimulationData
295
+ def compute_wave_amplitudes_at_each_port (
296
+ self ,
297
+ port_reference_impedances : PortDataArray ,
298
+ sim_data : SimulationData ,
299
+ wave_type : Literal ["pseudo" , "power" ] = "pseudo" ,
285
300
) -> tuple [PortDataArray , PortDataArray ]:
286
- """Compute the incident and reflected power wave amplitudes at each port.
301
+ """Compute the incident and reflected amplitudes at each port.
287
302
The computed amplitudes have not been normalized.
288
303
289
304
Parameters
@@ -292,11 +307,14 @@ def compute_power_wave_amplitudes_at_each_port(
292
307
Reference impedance at each port.
293
308
sim_data : :class:`.SimulationData`
294
309
Results from the simulation.
310
+ wave_type : Literal['pseudo', 'power']
311
+ The type of waves computed, either pseudo waves defined by Equation 53 and Equation 54 in [1],
312
+ or power waves defined by Equation 4.67 in [2].
295
313
296
314
Returns
297
315
-------
298
316
tuple[:class:`.PortDataArray`, :class:`.PortDataArray`]
299
- Incident (a) and reflected (b) power wave amplitudes at each port.
317
+ Incident (a) and reflected (b) wave amplitudes at each port.
300
318
"""
301
319
port_names = [port .name for port in self .ports ]
302
320
values = np .zeros (
@@ -331,14 +349,41 @@ def compute_power_wave_amplitudes_at_each_port(
331
349
V_numpy = np .where (negative_real_Z , - V_numpy , V_numpy )
332
350
Z_numpy = np .where (negative_real_Z , - Z_numpy , Z_numpy )
333
351
334
- F_numpy = TerminalComponentModeler ._compute_F (Z_numpy )
352
+ F_numpy = TerminalComponentModeler ._compute_F (Z_numpy , wave_type )
353
+
354
+ b_Zref = Z_numpy
355
+ if wave_type == "power" :
356
+ b_Zref = np .conj (Z_numpy )
335
357
358
+ # Equations 53 and 54 from [1]
336
359
# Equation 4.67 - Pozar - Microwave Engineering 4ed
337
360
a .values = F_numpy * (V_numpy + Z_numpy * I_numpy )
338
- b .values = F_numpy * (V_numpy - np . conj ( Z_numpy ) * I_numpy )
361
+ b .values = F_numpy * (V_numpy - b_Zref * I_numpy )
339
362
340
363
return a , b
341
364
365
+ def compute_power_wave_amplitudes_at_each_port (
366
+ self , port_reference_impedances : PortDataArray , sim_data : SimulationData
367
+ ) -> tuple [PortDataArray , PortDataArray ]:
368
+ """DEPRECATED: Compute the incident and reflected power wave amplitudes at each port.
369
+ The computed amplitudes have not been normalized.
370
+
371
+ Parameters
372
+ ----------
373
+ port_reference_impedances : :class:`.PortDataArray`
374
+ Reference impedance at each port.
375
+ sim_data : :class:`.SimulationData`
376
+ Results from the simulation.
377
+
378
+ Returns
379
+ -------
380
+ tuple[:class:`.PortDataArray`, :class:`.PortDataArray`]
381
+ Incident (a) and reflected (b) power wave amplitudes at each port.
382
+ """
383
+ return self .compute_wave_amplitudes_at_each_port (
384
+ port_reference_impedances , sim_data , wave_type = "power"
385
+ )
386
+
342
387
@staticmethod
343
388
def compute_port_VI (
344
389
port_out : TerminalPortType , sim_data : SimulationData
@@ -365,7 +410,7 @@ def compute_port_VI(
365
410
def compute_power_wave_amplitudes (
366
411
port : Union [LumpedPort , CoaxialLumpedPort ], sim_data : SimulationData
367
412
) -> tuple [FreqDataArray , FreqDataArray ]:
368
- """Compute the incident and reflected power wave amplitudes at a lumped port.
413
+ """DEPRECATED: Compute the incident and reflected power wave amplitudes at a lumped port.
369
414
The computed amplitudes have not been normalized.
370
415
371
416
Parameters
@@ -388,9 +433,10 @@ def compute_power_wave_amplitudes(
388
433
389
434
@staticmethod
390
435
def compute_power_delivered_by_port (
391
- port : Union [LumpedPort , CoaxialLumpedPort ], sim_data : SimulationData
436
+ port : Union [LumpedPort , CoaxialLumpedPort ],
437
+ sim_data : SimulationData ,
392
438
) -> FreqDataArray :
393
- """Compute the power delivered to the network by a lumped port.
439
+ """DEPRECATED: Compute the power delivered to the network by a lumped port.
394
440
395
441
Parameters
396
442
----------
@@ -412,7 +458,7 @@ def compute_power_delivered_by_port(
412
458
def ab_to_s (
413
459
a_matrix : TerminalPortDataArray , b_matrix : TerminalPortDataArray
414
460
) -> TerminalPortDataArray :
415
- """Get the scattering matrix given the power wave matrices."""
461
+ """Get the scattering matrix given the wave amplitude matrices."""
416
462
# Ensure dimensions are ordered properly
417
463
a_matrix = a_matrix .transpose (* TerminalPortDataArray ._dims )
418
464
b_matrix = b_matrix .transpose (* TerminalPortDataArray ._dims )
@@ -428,44 +474,63 @@ def ab_to_s(
428
474
429
475
@staticmethod
430
476
def s_to_z (
431
- s_matrix : TerminalPortDataArray , reference : Union [complex , PortDataArray ]
477
+ s_matrix : TerminalPortDataArray ,
478
+ reference : Union [complex , PortDataArray ],
479
+ wave_type : Literal ["pseudo" , "power" ] = "pseudo" ,
432
480
) -> DataArray :
433
- """Get the impedance matrix given the scattering matrix and a reference impedance."""
481
+ """Get the impedance matrix given the scattering matrix and a reference impedance.
482
+
483
+ Parameters
484
+ ----------
485
+ s_matrix : :class:`.TerminalPortDataArray`
486
+ Scattering matrix computed using either the pseudo or power wave formulation.
487
+ reference : Union[complex, :class:`.PortDataArray`]
488
+ The reference impedance used at each port.
489
+ wave_type : Literal['pseudo', 'power']
490
+ The type of wave amplitudes used for computing the scattering matrix, either pseudo waves
491
+ defined by Equation 53 and Equation 54 in [1] or power waves defined by Equation 4.67 in [2].
492
+ """
434
493
435
494
# Ensure dimensions are ordered properly
436
495
z_matrix = s_matrix .transpose (* TerminalPortDataArray ._dims ).copy (deep = True )
437
496
s_vals = z_matrix .values
438
- eye = np .eye (len (s_matrix .port_out .values ), len (s_matrix .port_in .values ))
497
+ eye = np .eye (len (s_matrix .port_out .values ), len (s_matrix .port_in .values ))[np .newaxis , :, :]
498
+ # Ensure that Zport, F, and Finv act as diagonal matrices when multiplying by left or right
499
+ shape_left = (len (s_matrix .f ), len (s_matrix .port_out ), 1 )
500
+ shape_right = (len (s_matrix .f ), 1 , len (s_matrix .port_in ))
501
+ # Setup the port reference impedance array (scalar)
439
502
if isinstance (reference , PortDataArray ):
440
- # From Equation 4.68 - Pozar - Microwave Engineering 4ed
441
- # Ensure that Zport, F, and Finv act as diagonal matrices when multiplying by left or right
442
- shape_left = (len (s_matrix .f ), len (s_matrix .port_out ), 1 )
443
- shape_right = (len (s_matrix .f ), 1 , len (s_matrix .port_in ))
444
503
Zport = reference .values .reshape (shape_right )
445
- F = TerminalComponentModeler ._compute_F (Zport ).reshape (shape_right )
504
+ F = TerminalComponentModeler ._compute_F (Zport , wave_type ).reshape (shape_right )
446
505
Finv = (1.0 / F ).reshape (shape_left )
447
- FinvSF = Finv * s_vals * F
448
- RHS = eye * np .conj (Zport ) + FinvSF * Zport
449
- LHS = eye - FinvSF
450
- z_vals = np .matmul (AbstractComponentModeler .inv (LHS ), RHS )
451
506
else :
452
- # Simpler case when all port impedances are the same
453
- z_vals = (
454
- np .matmul (AbstractComponentModeler .inv (eye - s_vals ), (eye + s_vals )) * reference
455
- )
507
+ Zport = reference
508
+ F = TerminalComponentModeler ._compute_F (Zport , wave_type )
509
+ Finv = 1.0 / F
510
+ # Use conjugfate when S matrix is power-wave based
511
+ if wave_type == "power" :
512
+ Zport_mod = np .conj (Zport )
513
+ else :
514
+ Zport_mod = Zport
515
+
516
+ # From equation 74 from [1] for pseudo waves
517
+ # From Equation 4.68 - Pozar - Microwave Engineering 4ed for power waves
518
+ FinvSF = Finv * s_vals * F
519
+ RHS = eye * Zport_mod + FinvSF * Zport
520
+ LHS = eye - FinvSF
521
+ z_vals = np .linalg .solve (LHS , RHS )
456
522
457
523
z_matrix .data = z_vals
458
524
return z_matrix
459
525
460
526
@cached_property
461
527
def port_reference_impedances (self ) -> PortDataArray :
462
- """The reference impedance used at each port for definining power wave amplitudes.
528
+ """The reference impedance used at each port for definining wave amplitudes.
463
529
464
530
Note
465
531
----
466
- By default, we choose reference impedances to be the conjugate of the load impedance.
467
- For wave ports, this corresponds with the conjugate of the characteristic impedance.
468
- This choice corresponds with Equation 4.64 - Pozar - Microwave Engineering 4ed.
532
+ By default, we choose reference impedances to be the load impedance.
533
+ For wave ports, this corresponds with the the characteristic impedance.
469
534
"""
470
535
return self ._port_reference_impedances (self .batch_data )
471
536
@@ -493,16 +558,19 @@ def _port_reference_impedances(self, batch_data: BatchData) -> PortDataArray:
493
558
# LumpedPorts have a constant reference impedance
494
559
port_impedances .loc [{"port" : port .name }] = np .full (len (self .freqs ), port .impedance )
495
560
496
- port_impedances = TerminalComponentModeler ._set_port_data_array_attributes (
497
- port_impedances .conj ()
498
- )
561
+ port_impedances = TerminalComponentModeler ._set_port_data_array_attributes (port_impedances )
499
562
return port_impedances
500
563
501
564
@staticmethod
502
- def _compute_F (Z_numpy : np .array ):
565
+ def _compute_F (Z_numpy : np .array , wave_type : Literal [ "pseudo" , "power" ] = "pseudo" ):
503
566
"""Helper to convert port impedance matrix to F, which is used for
504
- computing generalized scattering parameters."""
505
- return 1.0 / (2.0 * np .sqrt (np .real (Z_numpy )))
567
+ computing scattering parameters
568
+ """
569
+ # Defined in [2] after equation 4.67
570
+ if wave_type == "power" :
571
+ return 1.0 / (2.0 * np .sqrt (np .real (Z_numpy )))
572
+ # Equation 75 from [1]
573
+ return np .sqrt (np .real (Z_numpy )) / (2.0 * np .abs (Z_numpy ))
506
574
507
575
@cached_property
508
576
def _lumped_ports (self ) -> list [AbstractLumpedPort ]:
@@ -585,7 +653,7 @@ def get_antenna_metrics_data(
585
653
) -> AntennaMetricsData :
586
654
"""Calculate antenna parameters using superposition of fields from multiple port excitations.
587
655
588
- The method computes the radiated far fields and port excitation power wave amplitudes
656
+ The method computes the radiated far fields and port excitation wave amplitudes
589
657
for a superposition of port excitations, which can be used to analyze antenna radiation
590
658
characteristics.
591
659
@@ -621,7 +689,7 @@ def get_antenna_metrics_data(
621
689
else :
622
690
rad_mon = self .get_radiation_monitor_by_name (monitor_name )
623
691
624
- # Create data arrays for holding the superposition of all port power wave amplitudes
692
+ # Create data arrays for holding the superposition of all port wave amplitudes
625
693
f = list (rad_mon .freqs )
626
694
coords = {"f" : f , "port" : port_names }
627
695
a_sum = PortDataArray (np .zeros ((len (f ), len (port_names )), dtype = complex ), coords = coords )
@@ -632,8 +700,8 @@ def get_antenna_metrics_data(
632
700
sim_data_port = self .batch_data [self ._task_name (port = port )]
633
701
radiation_data = sim_data_port [rad_mon .name ]
634
702
635
- a , b = self .compute_power_wave_amplitudes_at_each_port (
636
- self .port_reference_impedances , sim_data_port
703
+ a , b = self .compute_wave_amplitudes_at_each_port (
704
+ self .port_reference_impedances , sim_data_port , wave_type = "pseudo"
637
705
)
638
706
# Select a possible subset of frequencies
639
707
a = a .sel (f = f )
@@ -652,7 +720,7 @@ def get_antenna_metrics_data(
652
720
a = scale_factor * a
653
721
b = scale_factor * b
654
722
655
- # Combine the possibly scaled directivity data and the power wave amplitudes
723
+ # Combine the possibly scaled directivity data and the wave amplitudes
656
724
if combined_directivity_data is None :
657
725
combined_directivity_data = scaled_directivity_data
658
726
else :
0 commit comments