@@ -121,7 +121,7 @@ def make_t_network_impedance_matrix(
121121 return np .array ([[z11 , z12 ], [z21 , z22 ]])
122122
123123
124- def calc_transmission_line_S_matrix_pseudo (Z0 , Zref1 , Zref2 , gamma , length ):
124+ def calc_transmission_line_S_matrix_pseudo (Z0 , Zref1 , Zref2 , gamma , length , symmetric = False ):
125125 """
126126 Calculate complete 2x2 S-parameter matrix for a transmission line
127127 using pseudo wave definition
@@ -141,6 +141,9 @@ def calc_transmission_line_S_matrix_pseudo(Z0, Zref1, Zref2, gamma, length):
141141 Propagation constant (can be frequency-dependent)
142142 length : float
143143 Length (scalar only)
144+ symmetric : bool, optional
145+ If True, use pseudo_symmetric scaling (F = 1/(2*sqrt(Z))) which ensures
146+ S12 = S21 for reciprocal networks. Default is False.
144147
145148 Returns:
146149 --------
@@ -163,17 +166,31 @@ def calc_transmission_line_S_matrix_pseudo(Z0, Zref1, Zref2, gamma, length):
163166 numerator_S22 = (Z0 ** 2 - Zref1 * Zref2 ) * tanh_gamma_ell + Z0 * (Zref1 - Zref2 )
164167 S22 = numerator_S22 / denom
165168
166- # Calculate S21 (transmission from port 1 to port 2)
167- numerator_S21 = (
168- np .sqrt (np .real (Zref1 ) / np .real (Zref2 )) * (np .abs (Zref2 ) / np .abs (Zref1 )) * 2 * Z0 * Zref1
169- )
170- S21 = numerator_S21 / (denom * cosh_gamma_ell )
169+ # Calculate S12 and S21 (off-diagonal transmission terms)
170+ if symmetric :
171+ # For pseudo_symmetric: F = 1/(2*sqrt(Z)), so F1/F2 = sqrt(Z2/Z1)
172+ # This gives S12 = S21 for reciprocal networks
173+ numerator_S12 = 2 * Z0 * np .sqrt (Zref1 * Zref2 )
174+ numerator_S21 = numerator_S12
175+ else :
176+ # For pseudo: F = sqrt(Re(Z))/(2|Z|)
177+ numerator_S12 = (
178+ np .sqrt (np .real (Zref1 ) / np .real (Zref2 ))
179+ * (np .abs (Zref2 ) / np .abs (Zref1 ))
180+ * 2
181+ * Z0
182+ * Zref1
183+ )
184+ numerator_S21 = (
185+ np .sqrt (np .real (Zref2 ) / np .real (Zref1 ))
186+ * (np .abs (Zref1 ) / np .abs (Zref2 ))
187+ * 2
188+ * Z0
189+ * Zref2
190+ )
171191
172- # Calculate S12 (transmission from port 2 to port 1)
173- numerator_S12 = (
174- np .sqrt (np .real (Zref2 ) / np .real (Zref1 )) * (np .abs (Zref1 ) / np .abs (Zref2 )) * 2 * Z0 * Zref2
175- )
176192 S12 = numerator_S12 / (denom * cosh_gamma_ell )
193+ S21 = numerator_S21 / (denom * cosh_gamma_ell )
177194
178195 # Construct the S-parameter matrix (nfreq, 2, 2)
179196 nfreq = len (np .atleast_1d (S11 ))
@@ -434,6 +451,7 @@ def test_complex_reference_s_to_z_component_modeler():
434451 skrf_S_50ohm = skrf .Network .from_z (z = Z , f = freqs )
435452 skrf_S_power = skrf .Network .from_z (z = Z , f = freqs , s_def = "power" , z0 = z0 )
436453 skrf_S_pseudo = skrf .Network .from_z (z = Z , f = freqs , s_def = "pseudo" , z0 = z0 )
454+ skrf_S_traveling = skrf .Network .from_z (z = Z , f = freqs , s_def = "traveling" , z0 = z0 )
437455
438456 ports = ["port1" , "port2" ]
439457 smatrix = TerminalPortDataArray (
@@ -454,6 +472,14 @@ def test_complex_reference_s_to_z_component_modeler():
454472 smatrix .values = skrf_S_pseudo .s
455473 z_tidy3d = s_to_z (smatrix , reference = z0_tidy3d , s_param_def = "pseudo" )
456474 assert np .all (np .isclose (z_tidy3d .values , Z ))
475+ # Our pseudo_symmetric name is equivalent to "traveling" definition in scikit-rf
476+ smatrix .values = skrf_S_traveling .s
477+ z_tidy3d = s_to_z (smatrix , reference = z0_tidy3d , s_param_def = "pseudo_symmetric" )
478+ assert np .all (np .isclose (z_tidy3d .values , Z ))
479+
480+ # Check that invalid s_param_def raises ValueError
481+ with pytest .raises (ValueError , match = "Unsupported S-parameter definition" ):
482+ s_to_z (smatrix , reference = z0_tidy3d , s_param_def = "invalid" )
457483
458484
459485def test_data_s_to_z (monkeypatch ):
@@ -1427,24 +1453,42 @@ def test_internal_construct_smatrix_with_port_vi(monkeypatch):
14271453 ]
14281454 )
14291455 Z0 = np .array (
1456+ [
1457+ 12.843105732941 + 15.394208173652j ,
1458+ 28.567192048123 + 9.1023847562915j ,
1459+ 31.209457618234 + 3.8475102934671j ,
1460+ ]
1461+ )
1462+ Z01 = np .array (
14301463 [
14311464 18.725191534567 + 12.672421364213j ,
14321465 34.038884625562 + 7.8654410284980j ,
14331466 35.725175635077 + 4.5490999181327j ,
14341467 ]
14351468 )
1469+ Z02 = np .array (
1470+ [
1471+ 24.156839210485 + 10.234195827361j ,
1472+ 41.892301567293 + 6.7812039451120j ,
1473+ 29.451276384019 + 5.1298475620183j ,
1474+ ]
1475+ )
14361476 # Break the reference impedance symmetry
1437- Zref = np .column_stack ((0.5 * Z0 , 2 * Z0 ))
1477+ Zref = np .column_stack ((Z01 , Z02 ))
14381478 # Calculate analytical S matrices for power and pseudo wave formulations
14391479 S_pseudo = calc_transmission_line_S_matrix_pseudo (Z0 , Zref [:, 0 ], Zref [:, 1 ], gamma , length )
1480+ S_pseudo_symmetric = calc_transmission_line_S_matrix_pseudo (
1481+ Z0 , Zref [:, 0 ], Zref [:, 1 ], gamma , length , symmetric = True
1482+ )
14401483 S_power = calc_transmission_line_S_matrix_power (Z0 , Zref [:, 0 ], Zref [:, 1 ], gamma , length )
1441-
1484+ Zref3 = Zref [:, :, np . newaxis ]
14421485 # Calculate A and B matrices where A is diagonal and B = S @ A
1486+
14431487 A = np .tile (np .eye (2 ), (len (freqs ), 1 , 1 )) # Identity matrix for each frequency
14441488 B = S_pseudo @ A
14451489 # Now get Voltages and Currents at each port due to excitations from each port
1446- Vscale = np .abs (Zref [:, :, np . newaxis ] ) / np .sqrt (np .real (Zref [:, :, np . newaxis ] ))
1447- Iscale = Vscale / Zref [:, :, np . newaxis ]
1490+ Vscale = np .abs (Zref3 ) / np .sqrt (np .real (Zref3 ))
1491+ Iscale = Vscale / Zref3
14481492 voltages = Vscale * (A + B ) # (f x port_out x port_in)
14491493 currents = Iscale * (A - B ) # (f x port_out x port_in)
14501494
@@ -1497,7 +1541,6 @@ def mock_port_impedances(modeler_data):
14971541 )
14981542
14991543 # Test the _internal_construct_smatrix method
1500- S_computed = modeler_data .smatrix ().data .values
15011544
15021545 def check_S_matrix (S_computed , S_expected , tol = 1e-12 ):
15031546 # Check that S-matrix has correct shape
@@ -1519,12 +1562,21 @@ def check_S_matrix(S_computed, S_expected, tol=1e-12):
15191562 )
15201563
15211564 # Check pseudo wave S matrix
1565+ S_computed = modeler_data .smatrix ().data .values
15221566 check_S_matrix (S_computed , S_pseudo )
15231567
15241568 # Check power wave S matrix
15251569 S_computed = modeler_data .smatrix (s_param_def = "power" ).data .values
15261570 check_S_matrix (S_computed , S_power )
15271571
1572+ # Check pseudo symmetric wave S matrix
1573+ S_computed = modeler_data .smatrix (s_param_def = "pseudo_symmetric" ).data .values
1574+ check_S_matrix (S_computed , S_pseudo_symmetric )
1575+
1576+ # Check that invalid s_param_def raises ValueError
1577+ with pytest .raises (ValueError , match = "Unsupported S-parameter definition" ):
1578+ modeler_data .smatrix (s_param_def = "invalid" )
1579+
15281580
15291581def test_wave_port_to_absorber (tmp_path ):
15301582 """Test that wave port absorber can be specified as a boolean, ABCBoundary, or ModeABCBoundary."""
0 commit comments