@@ -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 symmetric_pseudo 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 symmetric_pseudo: 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 symmetric_pseudo 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 = "symmetric_pseudo" )
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 ):
@@ -1450,24 +1476,42 @@ def test_internal_construct_smatrix_with_port_vi(monkeypatch):
14501476 ]
14511477 )
14521478 Z0 = np .array (
1479+ [
1480+ 12.843105732941 + 15.394208173652j ,
1481+ 28.567192048123 + 9.1023847562915j ,
1482+ 31.209457618234 + 3.8475102934671j ,
1483+ ]
1484+ )
1485+ Z01 = np .array (
14531486 [
14541487 18.725191534567 + 12.672421364213j ,
14551488 34.038884625562 + 7.8654410284980j ,
14561489 35.725175635077 + 4.5490999181327j ,
14571490 ]
14581491 )
1492+ Z02 = np .array (
1493+ [
1494+ 24.156839210485 + 10.234195827361j ,
1495+ 41.892301567293 + 6.7812039451120j ,
1496+ 29.451276384019 + 5.1298475620183j ,
1497+ ]
1498+ )
14591499 # Break the reference impedance symmetry
1460- Zref = np .column_stack ((0.5 * Z0 , 2 * Z0 ))
1500+ Zref = np .column_stack ((Z01 , Z02 ))
14611501 # Calculate analytical S matrices for power and pseudo wave formulations
14621502 S_pseudo = calc_transmission_line_S_matrix_pseudo (Z0 , Zref [:, 0 ], Zref [:, 1 ], gamma , length )
1503+ S_symmetric_pseudo = calc_transmission_line_S_matrix_pseudo (
1504+ Z0 , Zref [:, 0 ], Zref [:, 1 ], gamma , length , symmetric = True
1505+ )
14631506 S_power = calc_transmission_line_S_matrix_power (Z0 , Zref [:, 0 ], Zref [:, 1 ], gamma , length )
1464-
1507+ Zref3 = Zref [:, :, np . newaxis ]
14651508 # Calculate A and B matrices where A is diagonal and B = S @ A
1509+
14661510 A = np .tile (np .eye (2 ), (len (freqs ), 1 , 1 )) # Identity matrix for each frequency
14671511 B = S_pseudo @ A
14681512 # Now get Voltages and Currents at each port due to excitations from each port
1469- Vscale = np .abs (Zref [:, :, np . newaxis ] ) / np .sqrt (np .real (Zref [:, :, np . newaxis ] ))
1470- Iscale = Vscale / Zref [:, :, np . newaxis ]
1513+ Vscale = np .abs (Zref3 ) / np .sqrt (np .real (Zref3 ))
1514+ Iscale = Vscale / Zref3
14711515 voltages = Vscale * (A + B ) # (f x port_out x port_in)
14721516 currents = Iscale * (A - B ) # (f x port_out x port_in)
14731517
@@ -1520,7 +1564,6 @@ def mock_port_impedances(modeler_data):
15201564 )
15211565
15221566 # Test the _internal_construct_smatrix method
1523- S_computed = modeler_data .smatrix ().data .values
15241567
15251568 def check_S_matrix (S_computed , S_expected , tol = 1e-12 ):
15261569 # Check that S-matrix has correct shape
@@ -1542,12 +1585,21 @@ def check_S_matrix(S_computed, S_expected, tol=1e-12):
15421585 )
15431586
15441587 # Check pseudo wave S matrix
1588+ S_computed = modeler_data .smatrix ().data .values
15451589 check_S_matrix (S_computed , S_pseudo )
15461590
15471591 # Check power wave S matrix
15481592 S_computed = modeler_data .smatrix (s_param_def = "power" ).data .values
15491593 check_S_matrix (S_computed , S_power )
15501594
1595+ # Check symmetric_pseudo wave S matrix
1596+ S_computed = modeler_data .smatrix (s_param_def = "symmetric_pseudo" ).data .values
1597+ check_S_matrix (S_computed , S_symmetric_pseudo )
1598+
1599+ # Check that invalid s_param_def raises ValueError
1600+ with pytest .raises (ValueError , match = "Unsupported S-parameter definition" ):
1601+ modeler_data .smatrix (s_param_def = "invalid" )
1602+
15511603
15521604def test_wave_port_to_absorber (tmp_path ):
15531605 """Test that wave port absorber can be specified as a boolean, ABCBoundary, or ModeABCBoundary."""
0 commit comments