diff --git a/west_ic_antenna/antenna.py b/west_ic_antenna/antenna.py index e2fb14d..6264d4f 100644 --- a/west_ic_antenna/antenna.py +++ b/west_ic_antenna/antenna.py @@ -271,10 +271,8 @@ def capa( capa = pre ** cap ** post - # should we renormalize of not z0 to the Network's z0 they will be connected to? - # ANSYS Designer seems not doing it and leaves to 50 ohm - # renormalizing the z0 will lead to decrease the matched capacitances by ~10pF @55MHz - # In reality, values are closer to 50 pF at 55 MHz + # Renormalize z0s' to match left and right Network connected to + # If not renormalized, the match point would differ by ~1.4 pF at 55 MHz capa.renormalize(z_new=np.array([z0_bridge, z0_antenna]).T) return capa @@ -322,7 +320,6 @@ def _antenna_circuit(self, Cs: NumberLike) -> 'Circuit': # while for capa and voltage it is: # C1 C3 # C2 C4 - # service stub 3rd ports are left open connections = [ [(self.antenna, 0), (capa_C1, 1)], [(self.antenna, 1), (capa_C3, 1)], @@ -334,14 +331,9 @@ def _antenna_circuit(self, Cs: NumberLike) -> 'Circuit': [(capa_C4, 0), (self.bridge_right, 2)], [(self.bridge_left, 0), (self.windows_impedance_transformer_left, 1)], [(self.bridge_right, 0), (self.windows_impedance_transformer_right, 1)], - # [(self.windows_impedance_transformer_left, 0), (self.port_left, 0)], # no stub - # [(self.windows_impedance_transformer_right, 0), (self.port_right, 0)], # no stub [(self.windows_impedance_transformer_left, 0), (self.service_stub_left, 1)], + [(self.windows_impedance_transformer_right, 0), (self.service_stub_right, 1)], [(self.service_stub_left, 0), (self.port_left, 0)], - [ - (self.windows_impedance_transformer_right, 0), - (self.service_stub_right, 1), - ], [(self.service_stub_right, 0), (self.port_right, 0)], [(self.service_stub_left, 2), (self.short_left, 0)], [(self.service_stub_right, 2), (self.short_right, 0)], @@ -1683,45 +1675,47 @@ def match_both_sides_iterative( f_match: float = 55e6, power: NumberLike = [1, 1], phase: NumberLike = [0, np.pi], + Cs: Union[None, list] = None, solution_number: int = 1, - K: float = 0.7, z_T_target: float = Z_T_OPT, - C0: Union[None, list] = None, + K: float = 0.7, ) -> NumberLike: """ - + Match both sides using the automatic matching alg. (iterative). Parameters ---------- f_match : float, optional DESCRIPTION. The default is 55e6. - power : NumberLike, optional - DESCRIPTION. The default is [1, 1]. - phase : NumberLike, optional - DESCRIPTION. The default is [0, np.pi]. - solution_number : int, optional - DESCRIPTION. The default is 1. + power : list or array + Input power at external ports in Watts [W] + phase : list or array + Input phase at external ports in radian [rad] + Cs : list or array + antenna 4 capacitances [C1, C2, C3, C4] in [pF]. + Default is None (use internal Cs) + z_T_target : complex, optional + Desired target (Set Point) for the input impedance at T-junction. + The default is 2.89-0.17j. + solution_number : int + Desired solution. 1 for C_top > C_bot and 2 for the opposite. K : float, optional - DESCRIPTION. The default is 0.7. - z_T_target : float, optional - DESCRIPTION. The default is Z_T_OPT. - C0 : Union[None, list], optional - DESCRIPTION. The default is None. - : TYPE - DESCRIPTION. + Gain. Default value: 0.7. + Smaller value leads to higher number of iterations. Returns ------- - NumberLike - DESCRIPTION. + Cs_match : list or array + antenna 4 capacitances [C1, C2, C3, C4] in [pF]. """ + C0 = Cs or self.Cs - if C0 is not None: - if solution_number == 1: - C0 = [60, 40, 60, 40] # note that the start point matches solution 1 (Ctop>Cbot) - elif solution_number == 2: - C0 = [40, 60, 40, 60] + # if C0 is not None: + # if solution_number == 1: + # C0 = [60, 40, 60, 40] # note that the start point matches solution 1 (Ctop>Cbot) + # elif solution_number == 2: + # C0 = [40, 60, 40, 60] # creates an antenna circuit for a single frequency only to speed-up calculations freq_match = rf.Frequency(f_match, f_match, npoints=1, unit="Hz") diff --git a/west_ic_antenna/test/test_antenna.py b/west_ic_antenna/test/test_antenna.py index d67f54f..fbbaa33 100644 --- a/west_ic_antenna/test/test_antenna.py +++ b/west_ic_antenna/test/test_antenna.py @@ -106,9 +106,9 @@ def test_capa_model_bridge_antenna_ports(antenna_default_arg): # ANSYS Equivalent circuit with complex bridge and antenna ports' Z0 ANSYS_model = "ANSYS_benchmarks/WEST_capacitor_equivalent_circuit_bridge_antenna_ports.s2p" cap_ANSYS = rf.Network(os.path.join(TEST_DIR, ANSYS_model)) - + assert cap == cap_ANSYS - + def test_capa_model_bridge_antenna_ports_renorm50ohm(antenna_default_arg): """ @@ -129,21 +129,18 @@ def test_capa_model_bridge_antenna_ports_renorm50ohm(antenna_default_arg): def test_capa_model_connection(antenna_default_arg): """ Benchmark dummy circuit model of a antenna-capa-bridge vs ANSYS Circuit. - + In ANSYS, a single capacitor electrical circuit is connected to one bridge output and to an antenne front face input (using HFSS model). Bridge input is connected to input port and all other ports are shorted. - + Benchmark is performed when the Touchtone file is exported without and with 50 Ohm renormalization. """ - # NB: importing S-matrix in ANSYS Circuit as a N-port component - # requires the user to know in advance the port char impedance - # ant = antenna_default_arg ant = antenna_default_arg cap = ant.capa(C=50) - + # Creating the dummy circuit using scikit-rf cap.name = 'capa' port = rf.Circuit.Port(frequency=ant.frequency, name='port1', z0=ant.bridge.z0[:,0].real) @@ -157,20 +154,77 @@ def test_capa_model_connection(antenna_default_arg): ] cir = rf.Circuit(cnx) ntw = cir.network - + # ANSYS Model Export without renormalization ANSYS_model = "ANSYS_benchmarks/WEST_capacitor_test_connection.s1p" ANSYS_connection_test = rf.Network(os.path.join(TEST_DIR, ANSYS_model)) - - assert ntw == ANSYS_connection_test - + + assert ntw == ANSYS_connection_test + # ANSYS Model Export Renomalized to 50 Ohm ANSYS_model_50 = "ANSYS_benchmarks/WEST_capacitor_test_connection_renorm50ohm.s1p" ANSYS_connection_test_50 = rf.Network(os.path.join(TEST_DIR, ANSYS_model_50)) ntw.renormalize(50) - - assert ntw == ANSYS_connection_test_50 - + + assert ntw == ANSYS_connection_test_50 + +# Various return value tests +def test_antenna_circuit(antenna_default_arg): + """Test the internal circuit.""" + ant = antenna_default_arg + assert isinstance(ant.circuit(), rf.Circuit) + assert isinstance(ant.circuit(Cs=[50,50,50,50]), rf.Circuit) + + # TODO benchmark against ANSYS for a specific case of capa set + +def test_Cs(antenna_default_arg): + ant = antenna_default_arg + assert isinstance(ant.Cs, list) + + +# Matching Tests (longer...) +def test_match_one_side_left(antenna_default_arg): + # Test the return values of left side matching + Cs = antenna_default_arg.match_one_side( + f_match=55e6, + solution_number=1, + side="left", + z_match=29.89 + 0j + ) + # reference values 25/03/2024 + np.testing.assert_allclose(Cs, [51.44282751806046, 49.325275021316564, 150, 150], rtol=1e-5) + +def test_match_one_side_right(antenna_default_arg): + # Test the return values of right side matching + Cs = antenna_default_arg.match_one_side( + f_match=55e6, + solution_number=1, + side="right", + z_match=29.89 + 0j + ) + # reference values 25/03/2024 + np.testing.assert_allclose(Cs, [150, 150, 51.20960703555699, 49.489644871023714], rtol=1e-5) + +def test_match_both_sides_separately(antenna_default_arg): + # Test the return values of both sides matching (separately) + Cs = antenna_default_arg.match_both_sides_separately( + f_match=55e6, + solution_number=1, + z_match=[29.89 + 0j, 29.87 + 0j], + ) + # reference values 25/03/2024 + np.testing.assert_allclose(Cs, [51.44282792582701, 49.32527088864287, 51.20987339854108, 49.48946981138618], rtol=1e-1) + +def test_match_both_sides(antenna_default_arg): + # Test the return values of both sides matching + Cs = antenna_default_arg.match_both_sides( + f_match=55e6, + solution_number=1, + z_match=[29.89 + 0j, 29.87 + 0j], + ) + # reference values 25/03/2024 + np.testing.assert_allclose(Cs, [52.09695694, 50.08075395, 51.62129529, 50.25710599], rtol=1e-1) + if __name__ == "__main__": pytest.main([__file__])