From 675186b8db275e07326c7062f9156304582ac0bc Mon Sep 17 00:00:00 2001 From: Gary Allen Date: Fri, 4 Oct 2024 10:25:30 +0200 Subject: [PATCH 01/58] Add line_floating method to Media --- skrf/media/media.py | 75 +++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 75 insertions(+) diff --git a/skrf/media/media.py b/skrf/media/media.py index 5559e04c9..165401398 100644 --- a/skrf/media/media.py +++ b/skrf/media/media.py @@ -1001,6 +1001,81 @@ def line(self, d: NumberLike, unit: str = 'deg', result.renormalize(result.z0, s_def=s_def) return result + + def line_floating(self, d: NumberLike, unit: str = 'deg', + z0: NumberLike | str | None = None, **kwargs) -> Network: + r""" + Floating transmission line of a given length and impedance. + + This method returns a transmission line with floating shields. + This is a four-port network, as opposed to the two-port _line_ method. + Ports 1 and 3 are one side of the line, and electrically connect to + ports 2 and 4 on the other side, respectively. + + The units of `length` are interpreted according to the value + of `unit`. If `z0` is not None, then a line specified impedance + is produced. + + Parameters + ---------- + d : number + the length of transmission line (see unit argument) + unit : ['deg','rad','m','cm','um','in','mil','s','us','ns','ps'] + the units of d. See :func:`to_meters`, for details + z0 : number, string, or array-like or None + the characteristic impedance of the line, if different + from media.z0. To set z0 in terms of normalized impedance, + pass a string, like `z0='1+.2j'` + \*\*kwargs : key word arguments + passed to :func:`match`, which is called initially to create a + 'blank' network. + + Returns + ------- + line_floating : :class:`~skrf.network.Network` object + matched, floating transmission line of given length + + Examples + -------- + >>> my_media.line_floating(1, 'mm', z0=100) + >>> my_media.line_floating(90, 'deg', z0='2') # set z0 as normalized impedance + + """ + if isinstance(z0,str): + z0 = self.parse_z0(z0)* self.z0 + + if z0 is None: + z0 = self.z0 + + s_def = kwargs.pop('s_def', S_DEF_DEFAULT) + + # The use of either traveling or pseudo waves s-parameters definition + # is required here. + # The definition of the reflection coefficient for power waves has + # conjugation. + result = self.match(nports=4, z0 = z0, s_def='traveling', **kwargs) + + theta = self.electrical_length(self.to_meters(d=d, unit=unit)) + + # From AWR docs on TLINP4. The math below could + # be re-worked directly into its S-parameter formulation + y11 = 1 / (z0 * np.tanh(theta)) + y12 = -1 / (z0 * np.sinh(theta)) + y22 = y11 + y21 = y12 + + result.y = \ + np.array([[ y11, y12, -y11, -y12], + [ y21, y22, -y21, -y22], + [-y11, -y12, y11, y12], + [-y21, -y22, y21, y22]]).transpose().reshape(-1, 4, 4) + + # renormalize (or embed) into z0_port if required + if self.z0_port is not None: + result.renormalize(self.z0_port) + result.renormalize(result.z0, s_def=s_def) + + return result def delay_load(self, Gamma0: NumberLike, d: Number, unit: str = 'deg', **kwargs) -> Network: From e9d8c5cf45ccbc359bcc5d8e155e955c205adf8b Mon Sep 17 00:00:00 2001 From: Gary Allen Date: Fri, 4 Oct 2024 10:31:16 +0200 Subject: [PATCH 02/58] Fixed y-matrix formatting in line_floating --- skrf/media/media.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/skrf/media/media.py b/skrf/media/media.py index 165401398..f0c31a0f4 100644 --- a/skrf/media/media.py +++ b/skrf/media/media.py @@ -1066,9 +1066,9 @@ def line_floating(self, d: NumberLike, unit: str = 'deg', result.y = \ np.array([[ y11, y12, -y11, -y12], - [ y21, y22, -y21, -y22], - [-y11, -y12, y11, y12], - [-y21, -y22, y21, y22]]).transpose().reshape(-1, 4, 4) + [ y21, y22, -y21, -y22], + [-y11, -y12, y11, y12], + [-y21, -y22, y21, y22]]).transpose().reshape(-1, 4, 4) # renormalize (or embed) into z0_port if required if self.z0_port is not None: From 6e64797bcffa384a94928e5a86c0f99f06c8dc7f Mon Sep 17 00:00:00 2001 From: Gary Allen Date: Mon, 7 Oct 2024 16:12:56 +0200 Subject: [PATCH 03/58] Fix linting issues --- skrf/media/media.py | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/skrf/media/media.py b/skrf/media/media.py index f0c31a0f4..7f82b3dda 100644 --- a/skrf/media/media.py +++ b/skrf/media/media.py @@ -1001,9 +1001,9 @@ def line(self, d: NumberLike, unit: str = 'deg', result.renormalize(result.z0, s_def=s_def) return result - + def line_floating(self, d: NumberLike, unit: str = 'deg', - z0: NumberLike | str | None = None, **kwargs) -> Network: + z0: NumberLike | str | None = None, **kwargs) -> Network: r""" Floating transmission line of a given length and impedance. @@ -1063,19 +1063,19 @@ def line_floating(self, d: NumberLike, unit: str = 'deg', y12 = -1 / (z0 * np.sinh(theta)) y22 = y11 y21 = y12 - + result.y = \ np.array([[ y11, y12, -y11, -y12], [ y21, y22, -y21, -y22], [-y11, -y12, y11, y12], [-y21, -y22, y21, y22]]).transpose().reshape(-1, 4, 4) - + # renormalize (or embed) into z0_port if required if self.z0_port is not None: result.renormalize(self.z0_port) result.renormalize(result.z0, s_def=s_def) - return result + return result def delay_load(self, Gamma0: NumberLike, d: Number, unit: str = 'deg', **kwargs) -> Network: From b9bb90e8573795321a6ec08c29c958f3043927ef Mon Sep 17 00:00:00 2001 From: Julien Hillairet Date: Mon, 21 Oct 2024 19:01:37 +0200 Subject: [PATCH 04/58] Bump version to 1.4.0 --- conda.recipe/meta.yaml | 2 +- skrf/__init__.py | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/conda.recipe/meta.yaml b/conda.recipe/meta.yaml index b645ced50..37dad1ae0 100644 --- a/conda.recipe/meta.yaml +++ b/conda.recipe/meta.yaml @@ -1,5 +1,5 @@ {% set name = "scikit-rf" %} -{% set version = "1.3.0" %} +{% set version = "1.4.0" %} {% set hash_val = "8223204281599ba14b685d7e28b7e361" %} package: diff --git a/skrf/__init__.py b/skrf/__init__.py index 82d91832b..96b672c0b 100644 --- a/skrf/__init__.py +++ b/skrf/__init__.py @@ -3,7 +3,7 @@ implemented in Python. """ -__version__ = '1.3.0' +__version__ = '1.4.0' ## Import all module names for coherent reference of name-space #import io From 2c55eaaffc8070ea51cd8cda988c66ac7cdb7fbf Mon Sep 17 00:00:00 2001 From: Lansus Date: Tue, 29 Oct 2024 11:40:36 +0800 Subject: [PATCH 05/58] Include shallow_copy argument in Network.copy() method. --- skrf/network.py | 25 +++++++++++++++++++++++-- 1 file changed, 23 insertions(+), 2 deletions(-) diff --git a/skrf/network.py b/skrf/network.py index 505f9e93b..be7b63f0f 100644 --- a/skrf/network.py +++ b/skrf/network.py @@ -2043,13 +2043,30 @@ def is_lossless(self, tol: float = mf.ALMOST_ZERO) -> bool: return True ## CLASS METHODS - def copy(self) -> Network: + def copy(self, shallow_copy: bool = False) -> Network: """ Return a copy of this Network. Needed to allow pass-by-value for a Network instead of pass-by-reference + Parameters + ---------- + shallow_copy : bool, optional + If True, the method creates a new Network object with empty s-parameters that share the same shape + as the original Network, but without copying the actual s-parameters data. This is useful when you + plan to immediately modify the s-parameters after creating the Network, as a deep copy would be + unnecessary and costly. Using `shallow_copy` improves performance by leveraging lazy initialization + through `numpy's np.empty()`, which allocates virtual memory without immediate physical memory + allocation, deferring actual memory initialization until first access. This approach can significantly + enhance `copy()` performance when dealing with large `Network` objects, when you are intended for + immediate modification after the Network's creation. + + Note + ---- + If you require a complete copy of the `Network` instance or need to perform operation on the s-parameters + of the copied Network, it is essential not to use the `shallow_copy` parameter! + Returns ------- ntwk : :class:`Network` @@ -2058,7 +2075,11 @@ def copy(self) -> Network: """ ntwk = Network(z0=self.z0, s_def=self.s_def, comments=self.comments) - ntwk._s = self.s.copy() + ntwk._s = ( + np.empty(shape=self.s.shape, dtype=self.s.dtype) + if shallow_copy + else self.s.copy() + ) ntwk.frequency._f = self.frequency._f.copy() ntwk.frequency.unit = self.frequency.unit ntwk.port_modes = self.port_modes.copy() From 3c4c3e29dc2683fd85834c2ec96123693aabbefc Mon Sep 17 00:00:00 2001 From: Lansus Date: Tue, 29 Oct 2024 13:04:02 +0800 Subject: [PATCH 06/58] Using shallow_copy in connect()/innerconnect() method. --- skrf/network.py | 28 +++++++++++++++++----------- 1 file changed, 17 insertions(+), 11 deletions(-) diff --git a/skrf/network.py b/skrf/network.py index be7b63f0f..676db1e52 100644 --- a/skrf/network.py +++ b/skrf/network.py @@ -5093,13 +5093,15 @@ def connect(ntwkA: Network, k: int, ntwkB: Network, l: int, num: int = 1) -> Net s_def = 'traveling' # create output Network, from copy of input - ntwkC = ntwkA.copy() + # Since ntwkC's s-parameters will change later, use shallow_copy for speedup + ntwkC = ntwkA.copy(shallow_copy=True) # if networks' z0's are not identical, then connect a impedance # mismatch, which takes into account the effect of differing port # impedances. # import pdb;pdb.set_trace() - if not assert_z0_at_ports_equal(ntwkA, k, ntwkB, l): + z0_equal = not assert_z0_at_ports_equal(ntwkA, k, ntwkB, l) + if z0_equal: # connect a impedance mismatch, which will takes into account the # effect of differing port impedances mismatch = impedance_mismatch(ntwkA.z0[:, k], ntwkB.z0[:, l], s_def) @@ -5111,7 +5113,7 @@ def connect(ntwkA: Network, k: int, ntwkB: Network, l: int, num: int = 1) -> Net ntwkC.renumber(from_ports=[ntwkC.nports - 1] + list(range(k, ntwkC.nports - 1)), to_ports=list(range(k, ntwkC.nports))) # call s-matrix connection function - ntwkC.s = connect_s(ntwkC.s, k, ntwkB.s, l, num) + ntwkC.s = connect_s(ntwkC.s if z0_equal else ntwkA.s, k, ntwkB.s, l, num) # combine z0 arrays and remove ports which were `connected` ntwkC.z0 = np.hstack( @@ -5482,20 +5484,24 @@ def innerconnect(ntwkA: Network, k: int, l: int, num: int = 1) -> Network: if (l + num - 1 > ntwkA.nports - 1): raise IndexError('Port `l` out of range') + # 'power' is not supported, convert to supported definition and back afterwards + if ntwkA.s_def == 'power': + ntwkA = ntwkA.copy() + ntwkA.renormalize(ntwkA.z0, 'pseudo') + # create output Network, from copy of input - ntwkC = ntwkA.copy() + # Since ntwkC's s-parameters will change later, use shallow_copy for speedup + ntwkC = ntwkA.copy(shallow_copy=True) s_def_original = ntwkC.s_def - # 'power' is not supported, convert to supported definition and back afterwards - if ntwkC.s_def == 'power': - ntwkC.renormalize(ntwkC.z0, 'pseudo') + z0_equal = not (ntwkC.z0[:, k] == ntwkC.z0[:, l]).all() - if not (ntwkC.z0[:, k] == ntwkC.z0[:, l]).all(): + if z0_equal: # connect a impedance mismatch, which will takes into account the # effect of differing port impedances - mismatch = impedance_mismatch(ntwkC.z0[:, k], ntwkC.z0[:, l], ntwkC.s_def) - ntwkC.s = connect_s(ntwkC.s, k, mismatch, 0, num=-1) + mismatch = impedance_mismatch(ntwkA.z0[:, k], ntwkA.z0[:, l], ntwkA.s_def) + ntwkC.s = connect_s(ntwkA.s, k, mismatch, 0, num=-1) # the connect_s() put the mismatch's output port at the end of # ntwkC's ports. Fix the new port's impedance, then insert it # at position k where it belongs. @@ -5504,7 +5510,7 @@ def innerconnect(ntwkA: Network, k: int, l: int, num: int = 1) -> Network: to_ports=list(range(k, ntwkC.nports))) # call s-matrix connection function - ntwkC.s = innerconnect_s(ntwkC.s, k, l) + ntwkC.s = innerconnect_s(ntwkC.s if z0_equal else ntwkA.s, k, l) # update the characteristic impedance matrix ntwkC.z0 = np.delete(ntwkC.z0, list(range(k, k + 1)) + list(range(l, l + 1)), 1) From b04c5836619993f1aad20ead855c8107f87f00c4 Mon Sep 17 00:00:00 2001 From: Yuquan Zhu <50561614+Asachoo@users.noreply.github.com> Date: Tue, 29 Oct 2024 16:19:12 +0800 Subject: [PATCH 07/58] Apply suggestions from code review Co-authored-by: Julien Hillairet --- skrf/network.py | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/skrf/network.py b/skrf/network.py index 676db1e52..edf891b25 100644 --- a/skrf/network.py +++ b/skrf/network.py @@ -5100,8 +5100,8 @@ def connect(ntwkA: Network, k: int, ntwkB: Network, l: int, num: int = 1) -> Net # mismatch, which takes into account the effect of differing port # impedances. # import pdb;pdb.set_trace() - z0_equal = not assert_z0_at_ports_equal(ntwkA, k, ntwkB, l) - if z0_equal: + z0_equal = assert_z0_at_ports_equal(ntwkA, k, ntwkB, l) + if not z0_equal: # connect a impedance mismatch, which will takes into account the # effect of differing port impedances mismatch = impedance_mismatch(ntwkA.z0[:, k], ntwkB.z0[:, l], s_def) @@ -5113,7 +5113,7 @@ def connect(ntwkA: Network, k: int, ntwkB: Network, l: int, num: int = 1) -> Net ntwkC.renumber(from_ports=[ntwkC.nports - 1] + list(range(k, ntwkC.nports - 1)), to_ports=list(range(k, ntwkC.nports))) # call s-matrix connection function - ntwkC.s = connect_s(ntwkC.s if z0_equal else ntwkA.s, k, ntwkB.s, l, num) + ntwkC.s = connect_s(ntwkC.s if not z0_equal else ntwkA.s, k, ntwkB.s, l, num) # combine z0 arrays and remove ports which were `connected` ntwkC.z0 = np.hstack( @@ -5495,9 +5495,9 @@ def innerconnect(ntwkA: Network, k: int, l: int, num: int = 1) -> Network: s_def_original = ntwkC.s_def - z0_equal = not (ntwkC.z0[:, k] == ntwkC.z0[:, l]).all() + z0_equal = (ntwkC.z0[:, k] == ntwkC.z0[:, l]).all() - if z0_equal: + if not z0_equal: # connect a impedance mismatch, which will takes into account the # effect of differing port impedances mismatch = impedance_mismatch(ntwkA.z0[:, k], ntwkA.z0[:, l], ntwkA.s_def) From 36d5c90afefd3c173fa276c0cb533d5848c6ac5d Mon Sep 17 00:00:00 2001 From: Lansus Date: Tue, 29 Oct 2024 16:43:39 +0800 Subject: [PATCH 08/58] Fix typos. --- skrf/network.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/skrf/network.py b/skrf/network.py index edf891b25..af0183e32 100644 --- a/skrf/network.py +++ b/skrf/network.py @@ -5510,7 +5510,7 @@ def innerconnect(ntwkA: Network, k: int, l: int, num: int = 1) -> Network: to_ports=list(range(k, ntwkC.nports))) # call s-matrix connection function - ntwkC.s = innerconnect_s(ntwkC.s if z0_equal else ntwkA.s, k, l) + ntwkC.s = innerconnect_s(ntwkC.s if not z0_equal else ntwkA.s, k, l) # update the characteristic impedance matrix ntwkC.z0 = np.delete(ntwkC.z0, list(range(k, k + 1)) + list(range(l, l + 1)), 1) From 8b41e7d9628b478825e56483c19f6ee7802cb24b Mon Sep 17 00:00:00 2001 From: Yuquan Zhu <50561614+Asachoo@users.noreply.github.com> Date: Wed, 30 Oct 2024 09:20:39 +0800 Subject: [PATCH 09/58] Update skrf/network.py Co-authored-by: Franz --- skrf/network.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/skrf/network.py b/skrf/network.py index af0183e32..4354e1bdb 100644 --- a/skrf/network.py +++ b/skrf/network.py @@ -2043,7 +2043,7 @@ def is_lossless(self, tol: float = mf.ALMOST_ZERO) -> bool: return True ## CLASS METHODS - def copy(self, shallow_copy: bool = False) -> Network: + def copy(self, *, shallow_copy: bool = False) -> Network: """ Return a copy of this Network. From 3b25165c73c8945b2816225bc91ab8459cdc28ac Mon Sep 17 00:00:00 2001 From: Lansus Date: Wed, 30 Oct 2024 10:51:32 +0800 Subject: [PATCH 10/58] Added f_unit parsing in the read_pna_csv() method, and used the parsed f_unit in other functions to build the Network. --- skrf/constants.py | 2 +- skrf/io/csv.py | 66 +++++++++++++++++++++++++---------------------- 2 files changed, 36 insertions(+), 32 deletions(-) diff --git a/skrf/constants.py b/skrf/constants.py index 497261ab5..0d0480f57 100644 --- a/skrf/constants.py +++ b/skrf/constants.py @@ -111,7 +111,7 @@ S_DEF_HFSS_DEFAULT = 'traveling' FrequencyUnitT = Literal["Hz", "kHz", "MHz", "GHz", "THz"] -FREQ_UNITS = {"Hz": 1.0, "kHz": 1e3, "MHz": 1e6, "GHz": 1e9, "THz": 1e12} +FREQ_UNITS: dict[FrequencyUnitT, float] = {"Hz": 1.0, "kHz": 1e3, "MHz": 1e6, "GHz": 1e9, "THz": 1e12} SweepTypeT = Literal["lin", "log"] CoordT = Literal["cart", "polar"] diff --git a/skrf/io/csv.py b/skrf/io/csv.py index 65042d2bf..9d5970b70 100644 --- a/skrf/io/csv.py +++ b/skrf/io/csv.py @@ -47,6 +47,8 @@ """ +from __future__ import annotations + import os from warnings import warn @@ -54,6 +56,7 @@ from .. import mathFunctions as mf from .. import util +from ..constants import FREQ_UNITS, FrequencyUnitT from ..frequency import Frequency from ..network import Network @@ -83,6 +86,8 @@ def read_pna_csv(filename, *args, **kwargs): data : :class:`numpy.ndarray` An array containing the data. The meaning of which depends on the header. + f_unit : FrequencyUnitT + The frequency unit of the data, could be 'Hz', 'kHz', 'MHz', 'GHz' and 'THz' See Also -------- @@ -90,7 +95,7 @@ def read_pna_csv(filename, *args, **kwargs): Examples -------- - >>> header, comments, data = rf.read_pna_csv('myfile.csv') + >>> header, comments, data, f_unit = rf.read_pna_csv('myfile.csv') """ warn("deprecated", DeprecationWarning, stacklevel=2) with open(filename) as fid: @@ -134,7 +139,19 @@ def read_pna_csv(filename, *args, **kwargs): # pna uses unicode coding for degree symbol, but we dont need that header = header.replace('\xb0','deg').rstrip('\n').rstrip('\r') - return header, comments, data + units_dict: dict[str, FrequencyUnitT] = {k.lower(): k for k in FREQ_UNITS.keys()} + + # default unit + unit: FrequencyUnitT = "Hz" + try: + # Parse frequency unit from header, format is like: "Freq(Hz)" + unit_tmp = header.split(',')[0].strip('Freq')[1:-1].lower() + if unit_tmp in units_dict: + unit = units_dict[unit_tmp] + except Exception: + unit: FrequencyUnitT = "Hz" + + return header, comments, data, unit def pna_csv_2_df(filename): """ @@ -152,7 +169,7 @@ def pna_csv_2_df(filename): """ warn("deprecated", DeprecationWarning, stacklevel=2) from pandas import DataFrame, Index - header, comments, d = read_pna_csv(filename) + header, comments, d, f_unit = read_pna_csv(filename) names = header.split(',') @@ -163,10 +180,10 @@ def pna_csv_2_df(filename): def pna_csv_2_ntwks2(filename, *args, **kwargs): warn("deprecated", DeprecationWarning, stacklevel=2) df = pna_csv_2_df(filename, *args, **kwargs) - header, comments, d = read_pna_csv(filename) + header, comments, d, f_unit = read_pna_csv(filename) ntwk_dict = {} param_set=set([k[:3] for k in df.columns]) - f = df.index.values*1e-9 + f = df.index.values for param in param_set: try: s = mf.dbdeg_2_reim( @@ -179,7 +196,7 @@ def pna_csv_2_ntwks2(filename, *args, **kwargs): df[f'{param} (IMAG)'].values, ) - ntwk_dict[param] = Network(f=f, s=s, name=param, comments=comments) + ntwk_dict[param] = Network(f=f, s=s, name=param, comments=comments, f_unit=f_unit) try: @@ -211,13 +228,13 @@ def pna_csv_2_ntwks3(filename): """ - header, comments, d = read_pna_csv(filename) + header, comments, d, f_unit = read_pna_csv(filename) col_headers = pna_csv_header_split(filename) # set impedance to 50 Ohm (doesn't matter for now) z0 = np.ones(np.shape(d)[0])*50 - # read f values, convert to GHz - f = d[:,0]/1e9 + # read f values + f = d[:,0] name = os.path.splitext(os.path.basename(filename))[0] @@ -236,7 +253,7 @@ def pna_csv_2_ntwks3(filename): elif 's22' in h.lower() and 'db' in h.lower(): s[:,1,1] = mf.dbdeg_2_reim(d[:,k+1], d[:,k+2]) - n = Network(f=f,s=s,z0=z0, name = name) + n = Network(f=f,s=s,z0=z0, name = name, f_unit=f_unit) return n else: @@ -562,7 +579,7 @@ def pna_csv_header_split(filename): list of column names """ warn("deprecated", DeprecationWarning, stacklevel=2) - header, comments, d = read_pna_csv(filename) + header, comments, d, f_unit = read_pna_csv(filename) n_traces = d.shape[1] - 1 # because theres is one frequency column @@ -609,7 +626,7 @@ def pna_csv_2_ntwks(filename): """ warn("deprecated", DeprecationWarning, stacklevel=2) #TODO: check the data's format (Real-imag or db/angle , ..) - header, comments, d = read_pna_csv(filename) + header, comments, d, f_unit = read_pna_csv(filename) #import pdb;pdb.set_trace() names = pna_csv_header_split(filename) @@ -619,16 +636,16 @@ def pna_csv_2_ntwks(filename): if (d.shape[1]-1)/2 == 0 : # this isnt complex data - f = d[:,0]*1e-9 + f = d[:,0] if 'db' in header.lower(): s = mf.db_2_mag(d[:,1]) else: raise (NotImplementedError) name = os.path.splitext(os.path.basename(filename))[0] - return Network(f=f, s=s, name=name, comments=comments) + return Network(f=f, s=s, name=name, comments=comments, f_unit=f_unit) else: for k in range(int((d.shape[1]-1)/2)): - f = d[:,0]*1e-9 + f = d[:,0] name = names[k] print((names[k], names[k+1])) if 'db' in names[k].lower() and 'deg' in names[k+1].lower(): @@ -640,20 +657,14 @@ def pna_csv_2_ntwks(filename): s = d[:,k*2+1]+1j*d[:,k*2+2] ntwk_list.append( - Network(f=f, s=s, name=name, comments=comments) + Network(f=f, s=s, name=name, comments=comments, f_unit=f_unit) ) return ntwk_list def pna_csv_2_freq(filename): warn("deprecated", DeprecationWarning, stacklevel=2) - header, comments, d = read_pna_csv(filename) - #try to pull out frequency unit - cols = pna_csv_header_split(filename) - try: - f_unit = cols[0].split('(')[1].split(')')[0] - except Exception: - f_unit = 'hz' + header, comments, d, f_unit = read_pna_csv(filename) f = d[:,0] return Frequency.from_f(f, unit = f_unit) @@ -677,19 +688,12 @@ def pna_csv_2_scalar_ntwks(filename, *args, **kwargs): """ warn("deprecated", DeprecationWarning, stacklevel=2) - header, comments, d = read_pna_csv(filename) + header, comments, d, f_unit = read_pna_csv(filename) n_traces = d.shape[1] - 1 # because theres is one frequency column cols = pna_csv_header_split(filename) - - #try to pull out frequency unit - try: - f_unit = cols[0].split('(')[1].split(')')[0] - except Exception: - f_unit = 'hz' - f = d[:,0] freq = Frequency.from_f(f, unit = f_unit) From 7b288cba40f3d99e5d87e49a92de9034e805e411 Mon Sep 17 00:00:00 2001 From: Lansus Date: Thu, 31 Oct 2024 11:46:07 +0800 Subject: [PATCH 11/58] Update an unit test case. --- skrf/io/tests/test_csv.py | 20 ++++++++++++++++++-- 1 file changed, 18 insertions(+), 2 deletions(-) diff --git a/skrf/io/tests/test_csv.py b/skrf/io/tests/test_csv.py index 64298bcd3..017ed8e7f 100644 --- a/skrf/io/tests/test_csv.py +++ b/skrf/io/tests/test_csv.py @@ -1,5 +1,6 @@ import os import unittest +import warnings import numpy as np import pytest @@ -19,8 +20,8 @@ def setUp(self): name for pna_csv_reim.csv file, then reads it in. """ self.test_dir = os.path.dirname(os.path.abspath(__file__))+'/' - filename = os.path.join(self.test_dir, 'pna_csv_reim.csv') - self.acsv = rf.AgilentCSV(filename) + self.filename = os.path.join(self.test_dir, 'pna_csv_reim.csv') + self.acsv = rf.AgilentCSV(self.filename) def test_columns(self): """ @@ -64,3 +65,18 @@ def test_scalar_networks(self): This only tests for execution, not accuracy """ a = self.acsv.scalar_networks + + def test_read_pna_csv(self): + """ + This tests reading of a csv file written by an Agilient PNA. + """ + with warnings.catch_warnings(record=True) as w: + with self.assertWarns(DeprecationWarning): + header, comment, data, f_unit = rf.read_pna_csv(self.filename) + + self.assertEqual(header, 'Freq(Hz),"A,1"(REAL),"A,1"(IMAG),"R1,1"(REAL),"R1,1"(IMAG)') + self.assertEqual(comment, 'this is a comment\nline\n') + self.assertTrue((data == np.array([[750000000000, 1, 2, 3, 4], + [1100000000000, 5, 6, 7,8], + ])).all()) + self.assertEqual(f_unit, 'Hz') From 6e7aa8f554a5e7922e3b585cda3967ce3c53a656 Mon Sep 17 00:00:00 2001 From: Julien Hillairet Date: Fri, 1 Nov 2024 19:41:14 +0100 Subject: [PATCH 12/58] add tests --- skrf/media/media.py | 43 +++++++++++++---------- skrf/media/tests/test_all_construction.py | 3 ++ skrf/media/tests/test_media.py | 23 ++++++++++++ 3 files changed, 51 insertions(+), 18 deletions(-) diff --git a/skrf/media/media.py b/skrf/media/media.py index 7f82b3dda..7ddf4fdf1 100644 --- a/skrf/media/media.py +++ b/skrf/media/media.py @@ -974,8 +974,8 @@ def line(self, d: NumberLike, unit: str = 'deg', '`embed` will be removed in version 1.0', DeprecationWarning, stacklevel = 2) - if isinstance(z0,str): - z0 = parse_z0(z0)* self.z0 + if isinstance(z0, str): + z0 = parse_z0(z0) * self.z0 if z0 is None: z0 = self.z0 @@ -1024,7 +1024,7 @@ def line_floating(self, d: NumberLike, unit: str = 'deg', the units of d. See :func:`to_meters`, for details z0 : number, string, or array-like or None the characteristic impedance of the line, if different - from media.z0. To set z0 in terms of normalized impedance, + from `media.z0`. To set z0 in terms of normalized impedance, pass a string, like `z0='1+.2j'` \*\*kwargs : key word arguments passed to :func:`match`, which is called initially to create a @@ -1041,8 +1041,8 @@ def line_floating(self, d: NumberLike, unit: str = 'deg', >>> my_media.line_floating(90, 'deg', z0='2') # set z0 as normalized impedance """ - if isinstance(z0,str): - z0 = self.parse_z0(z0)* self.z0 + if isinstance(z0, str): + z0 = self.parse_z0(z0) * self.z0 if z0 is None: z0 = self.z0 @@ -1053,22 +1053,29 @@ def line_floating(self, d: NumberLike, unit: str = 'deg', # is required here. # The definition of the reflection coefficient for power waves has # conjugation. - result = self.match(nports=4, z0 = z0, s_def='traveling', **kwargs) + result = self.match(nports=4, z0=z0, s_def='traveling', **kwargs) theta = self.electrical_length(self.to_meters(d=d, unit=unit)) - # From AWR docs on TLINP4. The math below could - # be re-worked directly into its S-parameter formulation - y11 = 1 / (z0 * np.tanh(theta)) - y12 = -1 / (z0 * np.sinh(theta)) - y22 = y11 - y21 = y12 - - result.y = \ - np.array([[ y11, y12, -y11, -y12], - [ y21, y22, -y21, -y22], - [-y11, -y12, y11, y12], - [-y21, -y22, y21, y22]]).transpose().reshape(-1, 4, 4) + if np.abs(theta) < ZERO: + result.s = 1/2* np.array([[1, 1, 1, -1], + [1, 1, -1, 1], + [1, -1, 1, 1], + [-1, 1, 1, 1]] + ).transpose().reshape(-1,4,4) + else: + # From AWR docs on TLINP4. The math below could + # be re-worked directly into its S-parameter formulation + y11 = 1 / (z0 * np.tanh(theta)) + y12 = -1 / (z0 * np.sinh(theta)) + y22 = y11 + y21 = y12 + + result.y = \ + np.array([[ y11, y12, -y11, -y12], + [ y21, y22, -y21, -y22], + [-y11, -y12, y11, y12], + [-y21, -y22, y21, y22]]).transpose().reshape(-1, 4, 4) # renormalize (or embed) into z0_port if required if self.z0_port is not None: diff --git a/skrf/media/tests/test_all_construction.py b/skrf/media/tests/test_all_construction.py index 482ba4150..40432b049 100644 --- a/skrf/media/tests/test_all_construction.py +++ b/skrf/media/tests/test_all_construction.py @@ -59,6 +59,9 @@ def test_thru(self): def test_line(self): self.media.line(1) + def test_line_floating(self): + self.media.line_floating(1) + def test_delay_load(self): self.media.delay_load(1,2) diff --git a/skrf/media/tests/test_media.py b/skrf/media/tests/test_media.py index 53d1ed01b..4e738812b 100644 --- a/skrf/media/tests/test_media.py +++ b/skrf/media/tests/test_media.py @@ -368,6 +368,29 @@ def test_isolator(self): skrf_ntwk = self.dummy_media.isolator(name = name) self.assertEqual(name, skrf_ntwk.name) + def test_line_floating(self): + """ + Test the naming of the network. When circuit is used to connect a + topology of networks, they should have unique names. + """ + name = 'floating_line' + self.dummy_media.frequency = Frequency(1, 1, 1, unit='GHz') + skrf_ntwk = self.dummy_media.line_floating(90, 'deg', name=name) + self.assertEqual(name, skrf_ntwk.name) + + # Expected to be a 4-port network + self.assertEqual(skrf_ntwk.nports, 4) + + # zero length case + s_zero = 1/2 * np.array([[1, 1, 1, -1], + [1, 1, -1, 1], + [1, -1, 1, 1], + [-1, 1, 1, 1]] + ).transpose().reshape(-1,4,4) + for z0 in [50, 1]: + zero_length = self.dummy_media.line_floating(d=0, z0=z0) + assert_array_almost_equal(zero_length.s, s_zero) + def test_scalar_gamma_z0_media(self): """ From 8e2626078762f6548ebb660000445da025085ceb Mon Sep 17 00:00:00 2001 From: Julien Hillairet Date: Fri, 1 Nov 2024 23:52:57 +0100 Subject: [PATCH 13/58] fix test --- skrf/media/media.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/skrf/media/media.py b/skrf/media/media.py index 7ddf4fdf1..33ef32f0b 100644 --- a/skrf/media/media.py +++ b/skrf/media/media.py @@ -1057,7 +1057,7 @@ def line_floating(self, d: NumberLike, unit: str = 'deg', theta = self.electrical_length(self.to_meters(d=d, unit=unit)) - if np.abs(theta) < ZERO: + if np.abs(theta).all() < ZERO: result.s = 1/2* np.array([[1, 1, 1, -1], [1, 1, -1, 1], [1, -1, 1, 1], From 3a99bf4edb8969fb833a67d2587cd8b30fe325b6 Mon Sep 17 00:00:00 2001 From: Julien Hillairet Date: Fri, 1 Nov 2024 23:57:56 +0100 Subject: [PATCH 14/58] fix the documentation part about testing since I'm here --- doc/source/contributing/index.rst | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/doc/source/contributing/index.rst b/doc/source/contributing/index.rst index 0f186f39d..edfe583c9 100644 --- a/doc/source/contributing/index.rst +++ b/doc/source/contributing/index.rst @@ -126,7 +126,7 @@ To run all the tests (except the virtual instruments) cd scikit-rf # Create environment and install dependencies, needed only once. python -m venv .venv - python -e[test,visa,netw,xlsx,plot,docs,testspice] + pip install -e .[test,visa,netw,xlsx,plot,docs,testspice] --compile # Activate Environment, needed for all following steps. # on Linux Systems From 40bd2861a3cbc74e8e7c350ccafb70bf331a8985 Mon Sep 17 00:00:00 2001 From: Franz Forstmayr Date: Sun, 3 Nov 2024 23:56:11 +0100 Subject: [PATCH 15/58] Pass kind to interpolate method (again) --- skrf/network.py | 1 + 1 file changed, 1 insertion(+) diff --git a/skrf/network.py b/skrf/network.py index 4354e1bdb..7fb15a250 100644 --- a/skrf/network.py +++ b/skrf/network.py @@ -2905,6 +2905,7 @@ def interpolate(self, freq_or_n: Frequency | NumberLike, basis: str = 's', #Not supported by rational_interp is_rational = True else: + kwargs["kind"] = kind if kind is not None else "linear" f_interp = interp1d # interpret input From d32d398c1daf5dc9ccf233ba63c807ec6c9f7f8d Mon Sep 17 00:00:00 2001 From: Franz Forstmayr Date: Tue, 5 Nov 2024 21:14:47 +0100 Subject: [PATCH 16/58] Interpolation tests --- skrf/tests/test_network.py | 31 +++++++++++-------------------- 1 file changed, 11 insertions(+), 20 deletions(-) diff --git a/skrf/tests/test_network.py b/skrf/tests/test_network.py index 5b60f2c04..fd117fcb2 100644 --- a/skrf/tests/test_network.py +++ b/skrf/tests/test_network.py @@ -1461,11 +1461,17 @@ def test_add(self): self.assertTrue( ((a+[1+1j,2+2j]).s == np.array([[[2+3j]],[[5+6j]]])).all()) - def test_interpolate(self): - a = rf.N(f=[1,2],s=[1+2j, 3+4j],z0=1, f_unit="ghz") - freq = rf.F.from_f(np.linspace(1,2,4), unit='ghz') - b = a.interpolate(freq) - # TODO: numerically test for correct interpolation + def test_interpolate_linear(self): + net = rf.Network(f=[0, 1, 3, 4], s=[0,1,9,16], f_unit="Hz") + + interp = net.interpolate(rf.Frequency(0, 4, 5, unit="Hz"), kind="linear") + assert np.allclose(interp.s[2], 5.0) + + def test_interpolate_cubic(self): + net = rf.Network(f=[0, 1, 3, 4], s=[0,1,9,16], f_unit="Hz") + + interp = net.interpolate(rf.Frequency(0, 4, 5, unit="Hz"), kind="cubic") + assert np.allclose(interp.s[2], 4.0) def test_interpolate_rational(self): a = rf.N(f=np.linspace(1,2,5),s=np.linspace(0,1,5)*(1+1j),z0=1, f_unit="ghz") @@ -1479,21 +1485,6 @@ def test_interpolate_rational(self): self.assertTrue(all(np.diff(np.abs(b.s.flatten())) > 0)) self.assertTrue(b.z0[0] == a.z0[0]) - def test_interpolate_linear(self): - a = rf.N(f=[1,2],s=[1+2j, 3+4j],z0=[1,2], f_unit="ghz") - freq = rf.F.from_f(np.linspace(1,2,3,endpoint=True), unit='GHz') - b = a.interpolate(freq, kind='linear') - self.assertFalse(any(np.isnan(b.s))) - # Test that the endpoints are the equal - # Middle point can also be calculated in this case - self.assertTrue(b.s[0] == a.s[0]) - self.assertTrue(b.s[1] == 0.5*(a.s[0] + a.s[1])) - self.assertTrue(b.s[-1] == a.s[-1]) - # Check Z0 interpolation - self.assertTrue(b.z0[0] == a.z0[0]) - self.assertTrue(b.z0[1] == 0.5*(a.z0[0] + a.z0[1])) - self.assertTrue(b.z0[-1] == a.z0[-1]) - def test_interpolate_freq_cropped(self): a = rf.N(f=np.arange(20), s=np.arange(20)*(1+1j),z0=1, f_unit="ghz") freq = rf.F.from_f(np.linspace(1,2,3,endpoint=True), unit='GHz') From a4463f1d42be4ea9df0587f36f3042161eb0146b Mon Sep 17 00:00:00 2001 From: Julien Hillairet Date: Wed, 6 Nov 2024 08:31:09 +0100 Subject: [PATCH 17/58] bump version --- conda.recipe/meta.yaml | 2 +- skrf/__init__.py | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/conda.recipe/meta.yaml b/conda.recipe/meta.yaml index 37dad1ae0..9865db289 100644 --- a/conda.recipe/meta.yaml +++ b/conda.recipe/meta.yaml @@ -1,5 +1,5 @@ {% set name = "scikit-rf" %} -{% set version = "1.4.0" %} +{% set version = "1.4.1" %} {% set hash_val = "8223204281599ba14b685d7e28b7e361" %} package: diff --git a/skrf/__init__.py b/skrf/__init__.py index 96b672c0b..49ccb2f7e 100644 --- a/skrf/__init__.py +++ b/skrf/__init__.py @@ -3,7 +3,7 @@ implemented in Python. """ -__version__ = '1.4.0' +__version__ = '1.4.1' ## Import all module names for coherent reference of name-space #import io From 578fc41f06f84cf5a081fbec8362da1fb716a9a0 Mon Sep 17 00:00:00 2001 From: Thomas Schweiger Date: Fri, 8 Nov 2024 23:46:04 +0100 Subject: [PATCH 18/58] New circuit synthesizer write_spice_subcircuit_s_new() reduces node and component count growth due to controlled sources from quadratic to linear while the behavior of the synthesized circuits stays the same. --- skrf/vectorFitting.py | 164 ++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 164 insertions(+) diff --git a/skrf/vectorFitting.py b/skrf/vectorFitting.py index 2829e7a7c..20975eb4f 100644 --- a/skrf/vectorFitting.py +++ b/skrf/vectorFitting.py @@ -2260,6 +2260,170 @@ def plot_passivation(self, ax: Axes = None) -> Axes: ax.set_ylabel('Max. singular value') return ax + def write_spice_subcircuit_s_new(self, file: str, fitted_model_name: str = "s_equivalent") -> None: + """ + Creates an equivalent N-port subcircuit based on its vector fitted S parameter responses + in spice simulator netlist syntax + + Parameters + ---------- + file : str + Path and filename including file extension (usually .sNp) for the subcircuit file. + fitted_model_name: str + Name of the resulting model, default "s_equivalent" + + Returns + ------- + None + + Notes + ------- + This code passed some manual plausibility tests but more + testing might be useful to be confident that the circuits + synthesized with it behave exactly the same as the circuits + synthesized with the current write_spice_subcircuit_s method. + + More details about this code are available under this link: + + https://github.com/scikit-rf/scikit-rf/discussions/1172 + + Examples + -------- + Load and fit the `Network`, then export the equivalent subcircuit: + + >>> nw_3port = skrf.Network('my3port.s3p') + >>> vf = skrf.VectorFitting(nw_3port) + >>> vf.vector_fit(n_poles_real=1, n_poles_cmplx=4) + >>> vf.write_spectre_subcircuit_new_s('/my3port_model.sp') + + References + ---------- + .. [1] G. Antonini, "SPICE Equivalent Circuits of Frequency-Domain Responses", IEEE Transactions on + Electromagnetic Compatibility, vol. 45, no. 3, pp. 502-512, August 2003, + doi: https://doi.org/10.1109/TEMC.2003.815528 + + .. [2] C. -C. Chou and J. E. Schutt-Ainé, "Equivalent Circuit Synthesis of Multiport S Parameters in + Pole–Residue Form," in IEEE Transactions on Components, Packaging and Manufacturing Technology, + vol. 11, no. 11, pp. 1971-1979, Nov. 2021, doi: 10.1109/TCPMT.2021.3115113 + + .. [3] Romano D, Antonini G, Grossner U, Kovačević-Badstübner I. Circuit synthesis techniques of + rational models of electromagnetic systems: A tutorial paper. Int J Numer Model. 2019 + doi: https://doi.org/10.1002/jnm.2612 + + """ + + # List of subcircuits for the equivalent admittances + subcircuits = [] + + # Provides a unique subcircuit identifier (X1, X2, X3, ...) + def get_new_subckt_identifier(): + subcircuits.append(f'X{len(subcircuits) + 1}') + return subcircuits[-1] + + with open(file, 'w') as f: + # write title line + f.write('* EQUIVALENT CIRCUIT FOR VECTOR FITTED S-MATRIX\n') + f.write('* Created using scikit-rf vectorFitting.py\n\n') + + # Define the complete equivalent circuit as a subcircuit with one input node per port + # those port nodes are labeled p1, p2, p3, ... + # all ports share a common node for ground reference (node 0) + str_input_nodes = " ".join(map(lambda x: f'p{x + 1}', range(self.network.nports))) + + f.write(f'.SUBCKT {fitted_model_name} {str_input_nodes}\n') + for n in range(self.network.nports): + f.write(f'\n* Port network for port {n + 1}\n') + + # Port reference impedance Z0 + f.write(f'R{n + 1} p{n+1} a{n + 1} {np.real(self.network.z0[0, n])}\n') + + # CCVS implementing the reflected wave b. + # Also used as current sensor to measure the input current + f.write(f'H_b_{n + 1} a{n + 1} 0 V_c_{n + 1} 1\n') + + f.write(f'* Differential incident wave a sources for transfer from port {n + 1}\n') + + # CCVS and VCVS driving the transfer admittances with 2*a*sqrt(Z0) = V + I*Z0 + f.write(f'H_p_{n + 1} nt_p_{n + 1} nts_p_{n + 1} H_b_{n + 1} {np.real(self.network.z0[0, n])}\n') + f.write(f'E_p_{n + 1} nts_p_{n + 1} 0 p{n + 1} 0 1\n') + + # VCVS driving the transfer admittances with -2*a*sqrt(Z0) = -V - I*Z0 + f.write(f'E_n_{n + 1} 0 nt_n_{n + 1} nt_p_{n + 1} 0 1\n') + + f.write(f'* Current sensor on center node for transfer to port {n + 1}\n') + + # Current sensor for the transfer to current port + f.write(f'V_c_{n + 1} nt_c_{n + 1} 0 0\n') + + for j in range(self.network.nports): + f.write(f'* Transfer network from port {j + 1} to port {n + 1}\n') + + # Stacking order in VectorFitting class variables: + # s11, s12, s13, ..., s21, s22, s23, ... + i_response = n * self.network.nports + j + + # Start with proportional and constant term of the model + # H(s) = d + s * e model + # Y(s) = G + s * C equivalent admittance + g = self.constant_coeff[i_response] + c = self.proportional_coeff[i_response] + + # R for constant term + if g < 0: + f.write(f'R{n + 1}{j + 1} nt_n_{j + 1} nt_c_{n + 1} {np.abs(1 / g)}\n') + elif g > 0: + f.write(f'R{n + 1}{j + 1} nt_p_{j + 1} nt_c_{n + 1} {1 / g}\n') + + # C for proportional term + if c < 0: + f.write(f'C{n + 1}{j + 1} nt_n_{j + 1} nt_c_{n + 1} {np.abs(c)}\n') + elif c > 0: + f.write(f'C{n + 1}{j + 1} nt_p_{j + 1} nt_c_{n + 1} {c}\n') + + # Transfer admittances represented by poles and residues + for i_pole in range(len(self.poles)): + pole = self.poles[i_pole] + residue = self.residues[i_response, i_pole] + node = get_new_subckt_identifier() + + if np.real(residue) < 0.0: + # Multiplication with -1 required, otherwise the values for RLC would be negative. + # This gets compensated by inverting the transfer current direction for this subcircuit + residue = -1 * residue + node += f' nt_n_{j + 1} nt_c_{n + 1}' + else: + node += f' nt_p_{j + 1} nt_c_{n + 1}' + + if np.imag(pole) == 0.0: + # Real pole; Add rl_admittance + l = 1 / np.real(residue) + r = -1 * np.real(pole) / np.real(residue) + f.write(node + f' rl_admittance res={r} ind={l}\n') + else: + # Complex pole of a conjugate pair; Add rcl_vccs_admittance + r = -1 * np.real(pole) / np.real(residue) + c = 2 * np.real(residue) / (np.abs(pole) ** 2) + l = 1 / (2 * np.real(residue)) + b = -2 * (np.real(residue) * np.real(pole) + np.imag(residue) * np.imag(pole)) + gm = b * l * c + f.write(node + f' rcl_vccs_admittance res={r} cap={c} ind={l} gm={gm}\n') + + f.write('.ENDS {fitted_model_name}\n\n') + + # Subcircuit for an RLCG equivalent admittance of a complex-conjugate pole-residue pair + f.write('.SUBCKT rcl_vccs_admittance n_pos n_neg res=1e3 cap=1e-9 ind=100e-12 gm=1e-3\n') + f.write('L1 n_pos 1 {ind}\n') + f.write('C1 1 2 {cap}\n') + f.write('R1 2 n_neg {res}\n') + f.write('G1 n_pos n_neg 1 2 {gm}\n') + f.write('.ENDS rcl_vccs_admittance\n\n') + + # Subcircuit for an RL equivalent admittance of a real pole-residue pair + f.write('.SUBCKT rl_admittance n_pos n_neg res=1e3 ind=100e-12\n') + f.write('L1 n_pos 1 {ind}\n') + f.write('R1 1 n_neg {res}\n') + f.write('.ENDS rl_admittance\n\n') + def write_spice_subcircuit_s(self, file: str, fitted_model_name: str = "s_equivalent") -> None: """ Creates an equivalent N-port SPICE subcircuit based on its vector fitted S parameter responses. From 159fceb70fe959cd981c25a4f6d0f85213f5b2cb Mon Sep 17 00:00:00 2001 From: Franz Forstmayr Date: Sun, 10 Nov 2024 22:31:09 +0100 Subject: [PATCH 19/58] Add failing test --- skrf/tests/test_vectorfitting.py | 11 +++++++++++ 1 file changed, 11 insertions(+) diff --git a/skrf/tests/test_vectorfitting.py b/skrf/tests/test_vectorfitting.py index 785654b13..637212d4d 100644 --- a/skrf/tests/test_vectorfitting.py +++ b/skrf/tests/test_vectorfitting.py @@ -153,6 +153,17 @@ def test_passivity_enforcement(self): # check if model is now passive self.assertTrue(vf.is_passive()) + def test_autofit(self): + vf = skrf.VectorFitting(skrf.data.ring_slot) + vf.auto_fit() + + assert vf.get_model_order(vf.poles) == 6 + assert np.sum(vf.poles.imag == 0.0) == 0 + assert np.sum(vf.poles.imag > 0.0) == 3 + + assert np.allclose(vf.get_rms_error(), 1.2748979815157275e-06) + + suite = unittest.TestLoader().loadTestsFromTestCase(VectorFittingTestCase) unittest.TextTestRunner(verbosity=2).run(suite) From 8e0a1c78049a4eb8071b8fb7df81a0b451c3763e Mon Sep 17 00:00:00 2001 From: Franz Forstmayr Date: Sun, 10 Nov 2024 22:32:19 +0100 Subject: [PATCH 20/58] Use trapezoid rule from scipy --- skrf/vectorFitting.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/skrf/vectorFitting.py b/skrf/vectorFitting.py index 2829e7a7c..8a3cab472 100644 --- a/skrf/vectorFitting.py +++ b/skrf/vectorFitting.py @@ -7,6 +7,7 @@ from typing import TYPE_CHECKING, Any import numpy as np +from scipy.integrate import trapezoid try: from matplotlib.ticker import EngFormatter @@ -140,7 +141,7 @@ def get_spurious(poles: np.ndarray, residues: np.ndarray, n_freqs: int = 101, ga omega_eval = np.linspace(np.min(poles.imag) / 3, np.max(poles.imag) * 3, n_freqs) h = (residues[:, None, :] / (1j * omega_eval[:, None] - poles) + np.conj(residues[:, None, :]) / (1j * omega_eval[:, None] - np.conj(poles))) - norm2 = np.sqrt(np.trapezoid(h.real ** 2 + h.imag ** 2, omega_eval, axis=1)) + norm2 = np.sqrt(trapezoid(h.real ** 2 + h.imag ** 2, omega_eval, axis=1)) spurious = np.all(norm2 / np.mean(norm2) < gamma, axis=0) return spurious From 8afef5bac786f7e447a13fdac5b9abb97d29a3ce Mon Sep 17 00:00:00 2001 From: Lansus Date: Tue, 12 Nov 2024 09:58:39 +0800 Subject: [PATCH 21/58] Fix the issue of plot_s_db_time() method. --- skrf/plotting.py | 5 +++-- skrf/tests/test_plotting.py | 1 + 2 files changed, 4 insertions(+), 2 deletions(-) diff --git a/skrf/plotting.py b/skrf/plotting.py index 6221eb4f9..d9ff9fc16 100644 --- a/skrf/plotting.py +++ b/skrf/plotting.py @@ -989,8 +989,9 @@ def plot_reciprocity2(netw: Network, db=False, *args, **kwargs): plt.draw() -def plot_s_db_time(netw: Network, *args, window: str | float | tuple[str, float]=('kaiser', 6), - normalize: bool = True, center_to_dc: bool = None, **kwargs): +def plot_s_db_time(netw: Network, center_to_dc: bool = None, *args, + window: str | float | tuple[str, float]=('kaiser', 6), normalize: bool = True, + **kwargs): return netw.windowed(window, normalize, center_to_dc).plot_s_time_db(*args,**kwargs) diff --git a/skrf/tests/test_plotting.py b/skrf/tests/test_plotting.py index 2b6b5b760..bd3cfeecd 100644 --- a/skrf/tests/test_plotting.py +++ b/skrf/tests/test_plotting.py @@ -45,6 +45,7 @@ def test_plot_reciprocity2(ntwk1_dc: rf.Network, ): def test_plot_s_db_time(ntwk1_dc: rf.Network, ): ntwk1_dc.plot_s_db_time() + ntwk1_dc.plot_s_db_time(m=0, n=0) def test_plot_s_smith(ntwk1_dc: rf.Network, ): ntwk1_dc.plot_s_smith() From f0eab46468008e5304aad65c4d3614338f19535b Mon Sep 17 00:00:00 2001 From: Lansus Date: Tue, 12 Nov 2024 10:10:20 +0800 Subject: [PATCH 22/58] Issue fixes with smaller changes. --- skrf/network.py | 2 +- skrf/plotting.py | 5 ++--- 2 files changed, 3 insertions(+), 4 deletions(-) diff --git a/skrf/network.py b/skrf/network.py index 7fb15a250..125add880 100644 --- a/skrf/network.py +++ b/skrf/network.py @@ -4925,7 +4925,7 @@ def plot_reciprocity2(self, db=False, *args, **kwargs): @copy_doc(rfplt.plot_s_db_time) def plot_s_db_time(self, center_to_dc=None, *args, **kwargs): - return rfplt.plot_s_db_time(self, center_to_dc, *args, **kwargs) + return rfplt.plot_s_db_time(self, *args, center_to_dc=center_to_dc, **kwargs) @copy_doc(rfplt.plot_s_smith) def plot_s_smith(self, m=None, n=None,r=1, ax=None, show_legend=True,\ diff --git a/skrf/plotting.py b/skrf/plotting.py index d9ff9fc16..6221eb4f9 100644 --- a/skrf/plotting.py +++ b/skrf/plotting.py @@ -989,9 +989,8 @@ def plot_reciprocity2(netw: Network, db=False, *args, **kwargs): plt.draw() -def plot_s_db_time(netw: Network, center_to_dc: bool = None, *args, - window: str | float | tuple[str, float]=('kaiser', 6), normalize: bool = True, - **kwargs): +def plot_s_db_time(netw: Network, *args, window: str | float | tuple[str, float]=('kaiser', 6), + normalize: bool = True, center_to_dc: bool = None, **kwargs): return netw.windowed(window, normalize, center_to_dc).plot_s_time_db(*args,**kwargs) From 0059a244e42f14653d73f3f4baf0ff17eb71b6d4 Mon Sep 17 00:00:00 2001 From: mhuser Date: Tue, 12 Nov 2024 22:49:33 +0100 Subject: [PATCH 23/58] use RLGC distortion and nominal impedance in definedaeptandz0 media --- skrf/media/definedAEpTandZ0.py | 47 ++++++++++++++++++++++++++++++---- 1 file changed, 42 insertions(+), 5 deletions(-) diff --git a/skrf/media/definedAEpTandZ0.py b/skrf/media/definedAEpTandZ0.py index 4976bdd7c..09666624b 100644 --- a/skrf/media/definedAEpTandZ0.py +++ b/skrf/media/definedAEpTandZ0.py @@ -32,7 +32,7 @@ class DefinedAEpTandZ0(Media): Transmission line medium defined by A, Ep, Tand and Z0. This medium is defined by attenuation `A`, relative permittivity `Ep_r`, - loss angle `tand` and characteristic impedance `Z0`. + loss angle `tand` and nominal impedance `Z0`. Djirdjevic [#Djordjevic]_ / Svennson [#Svensson]_ dispersion model is provided for dielectric. Default behaviour is frequency invariant. @@ -86,14 +86,41 @@ class DefinedAEpTandZ0(Media): tanD : number, array-like, default 0.0 Dielectric relative permittivity loss tangent :math:`\tan\delta`. See `ep_r`. z0 : number, array-like, default 50.0 - Quasi-static characteristic impedance of the medium. + Quasi-static nominal impedance of the medium. + Because of the distortion introduced by the conductor and dielectric + losses, the characteristic impedance can be frequency-dependent with an + imaginary part. The characteristic impedance is computed with an RLGC + model. + + .. math:: + + R = 2 Z_n \alpha_{conductor} + + .. math:: + + L = \frac{Z_n \sqrt{\epsilon_r}}{C_0} + + .. math:: + + G = \frac{2}{Z_n} \alpha_{dielectric} + + .. math:: + + C = \frac{\sqrt{\epsilon_r}}{C_0 Z_n} + + .. math:: + + Z_0 = \sqrt{\frac{R + j\omega L}{G + j\omega C}} + + where :math:`Z_n` is the nominal impedance and :math:`Z_0` is the + characteristic impedance. Z0 : number, array-like, or None deprecated parameter, only emmit a deprecation warning. f_low : number, default 1e3, optional - Low frequency in Hz for for Djirdjevic/Svennson dispersion model. + Low frequency in Hz for for Djordjevic/Svennson dispersion model. See `ep_r`. f_high : number, default 1e12, optional - High frequency in Hz for for Djirdjevic/Svennson dispersion model. + High frequency in Hz for for Djordjevic/Svennson dispersion model. See `ep_r`. f_ep : number, default 1e9, , optional Specification frequency in Hz for for Djirdjevic/Svennson dispersion model. @@ -139,10 +166,20 @@ def __init__(self, frequency: Frequency| None = None, Media.__init__(self, frequency=frequency, z0_port=z0_port) self.A, self.f_A = A, f_A self.ep_r, self.tanD = ep_r, tanD - self.z0_characteristic = z0 + self.Zn = z0 self.f_low, self.f_high, self.f_ep = f_low, f_high, f_ep self.model = model + # z0 is the nominal impedance. Compute characteristic impedance with + # an RLGC model based on alpha conductor and alpha dielectric. + self.R = 2 * self.Zn * self.alpha_conductor + self.L = self.Zn * sqrt(self.ep_r) / c + self.G = 2 / self.Zn * self.alpha_dielectric + self.C = sqrt(ep_r) / c / self.Zn + self.z0_characteristic = sqrt( + (self.R + 1j * self.frequency.w * self.L) / + (self.G + 1j * self.frequency.w * self.C)) + if Z0 is not None: # warns of deprecation warnings.warn( From d3723edb9abc3513214bc9ca3fbe500376dc7fcf Mon Sep 17 00:00:00 2001 From: mhuser Date: Wed, 13 Nov 2024 10:54:27 +0100 Subject: [PATCH 24/58] typo in doctsting for attenuation --- skrf/media/definedAEpTandZ0.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/skrf/media/definedAEpTandZ0.py b/skrf/media/definedAEpTandZ0.py index 09666624b..9f096e8ad 100644 --- a/skrf/media/definedAEpTandZ0.py +++ b/skrf/media/definedAEpTandZ0.py @@ -51,7 +51,7 @@ class DefinedAEpTandZ0(Media): (Default is None) A : number, array-like, default 0.0 Attenuation due to conductor loss in dB/m/sqrt(Hz) - The attenuation :math:`A(f)`at frequency :math:`f` is: + The attenuation :math:`A(f)` at frequency :math:`f` is: .. math:: From 2ec425be23f600e9fd1217ac5af426bacb40ab2f Mon Sep 17 00:00:00 2001 From: mhuser Date: Wed, 13 Nov 2024 11:48:01 +0100 Subject: [PATCH 25/58] add DefinedAEpTandZ0 test against awr --- skrf/media/tests/awr/tlinp.s2p | 207 ++++++++++++++++++++++ skrf/media/tests/test_definedaeptandz0.py | 112 ++++++++++++ 2 files changed, 319 insertions(+) create mode 100644 skrf/media/tests/awr/tlinp.s2p create mode 100644 skrf/media/tests/test_definedaeptandz0.py diff --git a/skrf/media/tests/awr/tlinp.s2p b/skrf/media/tests/awr/tlinp.s2p new file mode 100644 index 000000000..f304ef8ab --- /dev/null +++ b/skrf/media/tests/awr/tlinp.s2p @@ -0,0 +1,207 @@ +! AWR Design Environment (17415) Thu Oct 17 09:15:28 2024 +! Document = 'TLine' +! nPorts: 2, nXvals: 201 + +# MHz S DB R 50 +!Freq DBS11 AngS11 DBS21 AngS21 DBS12 AngS12 DBS22 AngS22 +10 -45.0952624156537 -89.1519419491076 -0.00620899528653234 -1.63925681388553 -0.00620899528653137 -1.6392568138855 -45.0952624156528 -89.1519419491102 +14.95 -41.6102998374044 -90.8444540909481 -0.00826868959441112 -2.45076154045696 -0.00826868959441063 -2.45076154045707 -41.6102998374065 -90.8444540909475 +19.9 -39.1317154120894 -92.183211865309 -0.0102521133236231 -3.26220839095278 -0.0102521133236256 -3.26220839095272 -39.1317154120886 -92.1832118653082 +24.85 -37.207497576415 -93.3550441786291 -0.0122098778368871 -4.0735848759955 -0.0122098778368837 -4.07358487599545 -37.2074975764145 -93.3550441786306 +29.8 -35.6350998411762 -94.4325590395497 -0.0141678413536272 -4.88487823580607 -0.0141678413536272 -4.88487823580613 -35.6350998411767 -94.43255903955 +34.75 -34.3061016449838 -95.4506319879408 -0.016141200233436 -5.69607576667909 -0.016141200233436 -5.69607576667908 -34.3061016449837 -95.450631987941 +39.7 -33.1556371777342 -96.4283681724419 -0.0181396722271922 -6.50716490484516 -0.0181396722271922 -6.50716490484516 -33.1556371777342 -96.4283681724419 +44.65 -32.1417984679386 -97.3772235663188 -0.0201698176819533 -7.31813325088696 -0.0201698176819533 -7.31813325088695 -32.1417984679385 -97.377223566319 +49.6 -31.2359361446189 -98.3045377929358 -0.0222362218189262 -8.12896857765049 -0.0222362218189233 -8.12896857765057 -31.2359361446194 -98.3045377929365 +54.55 -30.4175964105473 -99.2152577780477 -0.0243421548613419 -8.9396588337971 -0.0243421548613448 -8.93965883379706 -30.417596410547 -99.215257778047 +59.5 -29.6716654904551 -100.112854170845 -0.0264899670923303 -9.75019214670331 -0.0264899670923313 -9.7501921467033 -29.6716654904551 -100.112854170844 +64.45 -28.9866586957235 -100.999842354229 -0.0286813385214369 -10.5605568258281 -0.0286813385214379 -10.5605568258282 -28.9866586957237 -100.999842354228 +69.4 -28.3536441757883 -101.878095044142 -0.0309174437891482 -11.3707413667972 -0.0309174437891478 -11.3707413667972 -28.3536441757882 -101.878095044142 +74.35 -27.7655382390571 -102.749038339782 -0.0331990651537033 -12.1807344561663 -0.0331990651537028 -12.1807344561663 -27.7655382390572 -102.749038339782 +79.3 -27.2166281994987 -103.613779327469 -0.0355266723514358 -12.9905249767521 -0.0355266723514358 -12.9905249767521 -27.2166281994987 -103.613779327469 +84.25 -26.7022399165705 -104.473191798227 -0.0379004805812124 -13.8001020133952 -0.0379004805812124 -13.8001020133952 -26.7022399165705 -104.473191798228 +89.2 -26.2185003738652 -105.327975415714 -0.0403204936126105 -14.6094548590379 -0.0403204936126095 -14.6094548590379 -26.2185003738653 -105.327975415714 +94.15 -25.7621644507583 -106.178697536582 -0.0427865365185193 -15.4185730210173 -0.0427865365185222 -15.4185730210173 -25.7621644507583 -106.178697536582 +99.1 -25.3304861241117 -107.025823391182 -0.0452982810099401 -16.2274462274848 -0.0452982810099425 -16.2274462274848 -25.3304861241117 -107.025823391182 +104.05 -24.9211210903222 -107.869738269704 -0.0478552653930253 -17.0360644338866 -0.0478552653930258 -17.0360644338866 -24.9211210903221 -107.869738269704 +109 -24.5320520362659 -108.710764102136 -0.0504569105492697 -17.8444178294476 -0.0504569105492682 -17.8444178294476 -24.5320520362659 -108.710764102136 +113.95 -24.1615305178926 -109.549172033239 -0.0531025329296984 -18.6524968436092 -0.0531025329296989 -18.6524968436092 -24.1615305178926 -109.549172033239 +118.9 -23.8080312054371 -110.385192088175 -0.0557913552762182 -19.4602921523853 -0.0557913552762196 -19.4602921523853 -23.8080312054372 -110.385192088175 +123.85 -23.4702154663214 -111.219020692537 -0.0585225155915977 -20.2677946846032 -0.0585225155915992 -20.2677946846032 -23.4702154663214 -111.219020692537 +128.8 -23.146902088481 -112.050826588113 -0.0612950747448859 -21.074995628001 -0.0612950747448873 -21.074995628001 -23.146902088481 -112.050826588113 +133.75 -22.8370435273739 -112.880755533997 -0.064108023002849 -21.8818864351613 -0.0641080230028485 -21.8818864351613 -22.8370435273739 -112.880755533998 +138.7 -22.5397064715107 -113.708934077388 -0.0669602857083159 -22.6884588292605 -0.066960285708315 -22.6884588292605 -22.5397064715107 -113.708934077388 +143.65 -22.2540558173856 -114.535472604256 -0.0698507282751327 -23.4947048096176 -0.0698507282751337 -23.4947048096176 -22.2540558173856 -114.535472604256 +148.6 -21.9793413604171 -115.360467827101 -0.0727781606313772 -24.3006166570282 -0.0727781606313762 -24.3006166570282 -21.9793413604171 -115.360467827101 +153.55 -21.7148866676494 -116.184004828677 -0.0757413412138497 -25.1061869388733 -0.0757413412138516 -25.1061869388733 -21.7148866676495 -116.184004828677 +158.5 -21.4600797166931 -117.006158752483 -0.0787389805951157 -25.9114085139917 -0.0787389805951172 -25.9114085139917 -21.4600797166931 -117.006158752483 +163.45 -21.2143649748695 -117.826996210058 -0.0817697448076968 -26.7162745373073 -0.0817697448076963 -26.7162745373073 -21.2143649748695 -117.826996210058 +168.4 -20.977236660656 -118.646576459527 -0.0848322584170559 -27.5207784642048 -0.0848322584170589 -27.5207784642048 -20.977236660656 -118.646576459527 +173.35 -20.7482329818466 -119.464952398114 -0.0879251073849553 -28.3249140546467 -0.0879251073849563 -28.3249140546467 -20.7482329818467 -119.464952398114 +178.3 -20.5269311853797 -120.282171402383 -0.0910468417568239 -29.1286753770274 -0.0910468417568244 -29.1286753770274 -20.5269311853797 -120.282171402383 +183.25 -20.3129432854359 -121.098276043067 -0.0941959782004642 -29.9320568117593 -0.0941959782004642 -29.9320568117593 -20.3129432854359 -121.098276043067 +188.2 -20.1059123613041 -121.913304696024 -0.0973710024184134 -30.7350530545883 -0.0973710024184134 -30.7350530545883 -20.1059123613041 -121.913304696024 +193.15 -19.9055093362435 -122.727292066687 -0.100570371452239 -31.5376591196351 -0.100570371452241 -31.5376591196351 -19.9055093362435 -122.727292066687 +198.1 -19.7114301642931 -123.540269642083 -0.103792515893782 -32.3398703421614 -0.103792515893782 -32.3398703421614 -19.7114301642931 -123.540269642083 +203.05 -19.5233933646096 -124.352266081949 -0.107035842015721 -33.141682381058 -0.107035842015721 -33.141682381058 -19.5233933646096 -124.352266081949 +208 -19.3411378531012 -125.163307558348 -0.110298733831677 -33.9430912210564 -0.110298733831677 -33.9430912210564 -19.3411378531011 -125.163307558348 +212.95 -19.1644210293981 -125.973418051586 -0.113579555094271 -34.7440931746604 -0.11357955509427 -34.7440931746604 -19.1644210293981 -125.973418051586 +217.9 -18.9930170839515 -126.782619608869 -0.116876651238153 -35.5446848838011 -0.116876651238153 -35.5446848838011 -18.9930170839516 -126.782619608869 +222.85 -18.8267154955898 -127.590932571069 -0.120188351273716 -36.3448633212137 -0.120188351273718 -36.3448633212137 -18.8267154955898 -127.590932571069 +227.8 -18.6653196944256 -128.398375772084 -0.123512969636322 -37.1446257915382 -0.123512969636322 -37.1446257915382 -18.6653196944256 -128.398375772084 +232.75 -18.5086458687854 -129.204966714567 -0.12684880799495 -37.9439699321457 -0.12684880799495 -37.9439699321457 -18.5086458687854 -129.204966714567 +237.7 -18.3565218979769 -130.010721725196 -0.130194157023491 -38.7428937136906 -0.130194157023492 -38.7428937136906 -18.3565218979769 -130.010721725196 +242.65 -18.2087863953322 -130.81565609218 -0.133547298137376 -39.541395440393 -0.133547298137375 -39.541395440393 -18.2087863953322 -130.81565609218 +247.6 -18.0652878481657 -131.619784187282 -0.136906505197712 -40.3394737500528 -0.136906505197711 -40.3394737500528 -18.0652878481657 -131.619784187282 +252.55 -17.9258838431396 -132.423119574305 -0.140270046184662 -41.1371276137976 -0.140270046184663 -41.1371276137976 -17.9258838431396 -132.423119574305 +257.5 -17.7904403670894 -133.225675105715 -0.143636184841511 -41.9343563355691 -0.14363618484151 -41.9343563355691 -17.7904403670894 -133.225675105715 +262.45 -17.6588311746913 -134.027463008822 -0.147003182290551 -42.7311595513499 -0.147003182290552 -42.7311595513499 -17.6588311746913 -134.027463008822 +267.4 -17.5309372154805 -134.828494962763 -0.15036929862167 -43.5275372281353 -0.150369298621671 -43.5275372281353 -17.5309372154805 -134.828494962763 +272.35 -17.4066461136901 -135.628782167326 -0.153732794454334 -44.3234896626534 -0.153732794454335 -44.3234896626535 -17.4066461136901 -135.628782167326 +277.3 -17.2858516952051 -136.428335404573 -0.157091932473484 -45.1190174798386 -0.157091932473485 -45.1190174798386 -17.2858516952051 -136.428335404573 +282.25 -17.1684535566328 -137.227165094022 -0.160444978939714 -45.9141216310596 -0.160444978939717 -45.9141216310596 -17.1684535566329 -137.227165094022 +287.2 -17.0543566720982 -138.025281342112 -0.163790205174001 -46.7088033921118 -0.163790205174002 -46.7088033921118 -17.0543566720982 -138.025281342112 +292.15 -16.9434710339007 -138.822693986552 -0.167125889017133 -47.5030643609726 -0.167125889017133 -47.5030643609726 -16.9434710339008 -138.822693986552 +297.1 -16.835711323621 -139.619412636082 -0.170450316263939 -48.2969064553288 -0.17045031626394 -48.2969064553288 -16.835711323621 -139.619412636082 +302.05 -16.7309966106632 -140.415446706116 -0.173761782072281 -49.0903319098789 -0.17376178207228 -49.0903319098789 -16.7309966106632 -140.415446706116 +307 -16.6292500755593 -141.210805450682 -0.177058592346843 -49.8833432734155 -0.177058592346841 -49.8833432734155 -16.6292500755593 -141.210805450682 +311.95 -16.5303987556645 -142.005497991016 -0.180339065097535 -50.6759434056933 -0.180339065097534 -50.6759434056933 -16.5303987556645 -142.005497991016 +316.9 -16.434373311131 -142.799533341136 -0.183601531772523 -51.4681354740872 -0.183601531772525 -51.4681354740872 -16.434373311131 -142.799533341136 +321.85 -16.3411078092796 -143.592920430669 -0.186844338565597 -52.2599229500465 -0.186844338565596 -52.2599229500465 -16.3411078092796 -143.592920430669 +326.8 -16.250539525688 -144.385668125208 -0.190065847697828 -53.0513096053499 -0.190065847697828 -53.0513096053499 -16.250539525688 -144.385668125207 +331.75 -16.1626087604933 -145.177785244382 -0.193264438673287 -53.8422995081667 -0.193264438673287 -53.8422995081666 -16.1626087604933 -145.177785244382 +336.7 -16.0772586685606 -145.969280577887 -0.196438509508642 -54.6328970189294 -0.196438509508642 -54.6328970189295 -16.0772586685606 -145.969280577887 +341.65 -15.9944351023096 -146.760162899619 -0.199586477936473 -55.4231067860245 -0.199586477936471 -55.4231067860245 -15.9944351023096 -146.760162899619 +346.6 -15.9140864661109 -147.550440980083 -0.202706782582069 -56.2129337413036 -0.202706782582069 -56.2129337413037 -15.9140864661109 -147.550440980083 +351.55 -15.8361635812743 -148.340123597223 -0.205797884113576 -57.0023830954249 -0.205797884113577 -57.0023830954249 -15.8361635812743 -148.340123597222 +356.5 -15.7606195607453 -149.129219545801 -0.208858266365261 -57.7914603330258 -0.208858266365262 -57.7914603330258 -15.7606195607453 -149.129219545801 +361.45 -15.6874096927133 -149.917737645435 -0.211886437433751 -58.5801712077357 -0.211886437433752 -58.5801712077357 -15.6874096927133 -149.917737645435 +366.4 -15.6164913324106 -150.705686747403 -0.214880930747062 -59.3685217370315 -0.214880930747062 -59.3685217370315 -15.6164913324106 -150.705686747403 +371.35 -15.547823801449 -151.493075740313 -0.217840306106265 -60.156518196944 -0.217840306106265 -60.156518196944 -15.547823801449 -151.493075740313 +376.3 -15.4813682941026 -152.279913554708 -0.220763150699639 -60.944167116618 -0.22076315069964 -60.944167116618 -15.4813682941026 -152.279913554708 +381.25 -15.417087789999 -153.066209166709 -0.223648080089165 -61.7314752727322 -0.223648080089166 -61.7314752727322 -15.417087789999 -153.066209166709 +386.2 -15.3549469727314 -153.851971600734 -0.226493739169254 -62.5184496837855 -0.226493739169255 -62.5184496837855 -15.3549469727314 -153.851971600734 +391.15 -15.2949121539462 -154.63720993138 -0.229298803097577 -63.3050976042526 -0.229298803097575 -63.3050976042526 -15.2949121539462 -154.63720993138 +396.1 -15.2369512025024 -155.421933284522 -0.232061978197891 -64.0914265186157 -0.232061978197892 -64.0914265186157 -15.2369512025024 -155.421933284522 +401.05 -15.1810334783329 -156.206150837661 -0.234782002834806 -64.8774441352765 -0.234782002834806 -64.8774441352765 -15.1810334783329 -156.206150837661 +406 -15.1271297706713 -156.989871819602 -0.237457648260363 -65.6631583803544 -0.237457648260364 -65.6631583803544 -15.1271297706713 -156.989871819602 +410.95 -15.0752122403371 -157.773105509472 -0.240087719432401 -66.4485773913743 -0.2400877194324 -66.4485773913743 -15.0752122403371 -157.773105509472 +415.9 -15.0252543657961 -158.555861235143 -0.242671055804625 -67.2337095108507 -0.242671055804625 -67.2337095108507 -15.0252543657961 -158.555861235143 +420.85 -14.9772308927412 -159.338148371074 -0.245206532088357 -68.0185632797715 -0.245206532088357 -68.0185632797715 -14.9772308927412 -159.338148371074 +425.8 -14.9311177869553 -160.119976335625 -0.247693058985931 -68.8031474309864 -0.247693058985931 -68.8031474309864 -14.9311177869553 -160.119976335625 +430.75 -14.8868921902422 -160.901354587854 -0.250129583895687 -69.5874708825059 -0.250129583895687 -69.5874708825059 -14.8868921902422 -160.901354587854 +435.7 -14.8445323792262 -161.682292623842 -0.252515091588574 -70.3715427307128 -0.252515091588573 -70.3715427307128 -14.8445323792262 -161.682292623842 +440.65 -14.8040177268399 -162.462799972552 -0.254848604856347 -71.1553722434934 -0.254848604856347 -71.1553722434934 -14.8040177268399 -162.462799972552 +445.6 -14.765328666332 -163.242886191254 -0.257129185131384 -71.9389688532914 -0.257129185131383 -71.9389688532914 -14.765328666332 -163.242886191254 +450.55 -14.7284466576437 -164.022560860544 -0.259355933078085 -72.7223421500883 -0.259355933078085 -72.7223421500883 -14.7284466576437 -164.022560860544 +455.5 -14.6933541560116 -164.801833578955 -0.261527989155913 -73.5055018743161 -0.261527989155914 -73.5055018743161 -14.6933541560116 -164.801833578955 +460.45 -14.6600345826697 -165.580713957194 -0.263644534154103 -74.288457909705 -0.263644534154104 -74.288457909705 -14.6600345826697 -165.580713957194 +465.4 -14.6284722975307 -166.35921161201 -0.265704789698028 -75.0712202760712 -0.265704789698029 -75.0712202760712 -14.6284722975307 -166.35921161201 +470.35 -14.5986525737382 -167.13733615971 -0.267708018727324 -75.8537991220478 -0.267708018727325 -75.8537991220478 -14.5986525737382 -167.13733615971 +475.3 -14.5705615739911 -167.915097209338 -0.269653525945734 -76.6362047177632 -0.269653525945735 -76.6362047177632 -14.5705615739911 -167.915097209338 +480.25 -14.5441863285467 -168.692504355509 -0.271540658242787 -77.4184474474719 -0.271540658242787 -77.4184474474719 -14.5441863285467 -168.692504355509 +485.2 -14.5195147148203 -169.469567170939 -0.273368805087321 -78.200537802139 -0.27336880508732 -78.200537802139 -14.5195147148203 -169.469567170939 +490.15 -14.4965354385047 -170.246295198642 -0.275137398892903 -78.9824863719844 -0.275137398892903 -78.9824863719844 -14.4965354385047 -170.246295198642 +495.1 -14.4752380161371 -171.022697943829 -0.276845915355217 -79.7643038389893 -0.276845915355215 -79.7643038389893 -14.4752380161371 -171.022697943829 +500.05 -14.4556127590533 -171.798784865498 -0.278493873761436 -80.5460009693691 -0.278493873761437 -80.5460009693691 -14.4556127590533 -171.798784865498 +505 -14.4376507586673 -172.574565367725 -0.280080837271683 -81.3275886060151 -0.280080837271684 -81.3275886060151 -14.4376507586673 -172.574565367725 +509.95 -14.4213438730255 -173.350048790652 -0.281606413172598 -82.1090776609095 -0.281606413172596 -82.1090776609095 -14.4213438730255 -173.350048790652 +514.9 -14.4066847145874 -174.125244401185 -0.283070253103084 -82.8904791075177 -0.283070253103082 -82.8904791075177 -14.4066847145874 -174.125244401185 +519.85 -14.3936666391901 -174.900161383391 -0.284472053252276 -83.6718039731591 -0.284472053252276 -83.6718039731591 -14.3936666391901 -174.900161383391 +524.8 -14.3822837361567 -175.674808828596 -0.285811554529806 -84.4530633313618 -0.285811554529805 -84.4530633313618 -14.3822837361567 -175.674808828596 +529.75 -14.3725308195165 -176.449195725189 -0.287088542708401 -85.2342682942037 -0.2870885427084 -85.2342682942037 -14.3725308195165 -176.449195725189 +534.7 -14.3644034203048 -177.223330948118 -0.288302848538879 -86.015430004643 -0.28830284853888 -86.015430004643 -14.3644034203048 -177.223330948118 +539.65 -14.3578977799186 -177.997223248075 -0.289454347837589 -86.7965596288415 -0.289454347837589 -86.7965596288415 -14.3578977799186 -177.997223248075 +544.6 -14.3530108445026 -178.770881240381 -0.290542961546342 -87.577668348485 -0.290542961546344 -87.577668348485 -14.3530108445026 -178.770881240381 +549.55 -14.3497402603494 -179.544313393535 -0.291568655764908 -88.3587673531009 -0.291568655764909 -88.3587673531009 -14.3497402603494 -179.544313393535 +554.5 -14.3480843702975 179.682471982552 -0.292531441756084 -89.1398678323803 -0.292531441756085 -89.1398678323803 -14.3480843702975 179.682471982552 +559.45 -14.3480422111148 178.909466748674 -0.293431375923405 -89.920980968505 -0.293431375923407 -89.920980968505 -14.3480422111148 178.909466748674 +564.4 -14.3496135118596 178.136662948781 -0.294268559761555 -90.7021179284821 -0.294268559761557 -90.7021179284821 -14.3496135118596 178.136662948781 +569.35 -14.3527986932143 177.364052822808 -0.295043139779505 -91.4832898564924 -0.295043139779505 -91.4832898564924 -14.3527986932143 177.364052822808 +574.3 -14.3575988677887 176.591628819864 -0.295755307396378 -92.264507866252 -0.295755307396379 -92.264507866252 -14.3575988677887 176.591628819864 +579.25 -14.364015841395 175.819383611825 -0.296405298810189 -93.0457830333932 -0.296405298810189 -93.0457830333932 -14.364015841395 175.819383611825 +584.2 -14.3720521152994 175.047310107331 -0.296993394839371 -93.8271263878663 -0.29699339483937 -93.8271263878663 -14.3720521152994 175.047310107331 +589.15 -14.3817108894573 174.275401466207 -0.297519920737233 -94.6085489063658 -0.297519920737233 -94.6085489063658 -14.3817108894573 174.275401466207 +594.1 -14.3929960667437 173.503651114344 -0.297985245979304 -95.3900615047844 -0.297985245979306 -95.3900615047844 -14.3929960667437 173.503651114344 +599.05 -14.4059122581944 172.732052759043 -0.298389784023667 -96.1716750306981 -0.298389784023668 -96.1716750306981 -14.4059122581944 172.732052759043 +604 -14.4204647892739 171.96060040487 -0.298733992044266 -96.953400255885 -0.298733992044266 -96.953400255885 -14.4204647892739 171.96060040487 +608.95 -14.4366597071949 171.189288370027 -0.299018370637264 -97.7352478688825 -0.299018370637263 -97.7352478688825 -14.4366597071949 171.189288370027 +613.9 -14.4545037893113 170.418111303293 -0.299243463500479 -98.5172284675839 -0.299243463500479 -98.5172284675839 -14.4545037893113 170.418111303293 +618.85 -14.4740045526174 169.647064201551 -0.299409857085916 -99.2993525518807 -0.299409857085917 -99.2993525518807 -14.4740045526175 169.647064201551 +623.8 -14.495170264384 168.876142427948 -0.299518180225474 -100.081630516351 -0.299518180225473 -100.081630516351 -14.495170264384 168.876142427948 +628.75 -14.5180099539703 168.105341730733 -0.299569103729866 -100.864072643002 -0.299569103729867 -100.864072643002 -14.5180099539703 168.105341730733 +633.7 -14.5425334258544 167.3346582628 -0.299563339960755 -101.646689094061 -0.299563339960754 -101.646689094061 -14.5425334258544 167.3346582628 +638.65 -14.5687512739267 166.564088602002 -0.299501642376249 -102.429489904831 -0.299501642376247 -102.429489904831 -14.5687512739267 166.564088602002 +643.6 -14.5966748971006 165.793629772287 -0.29938480504971 -103.212484976607 -0.299384805049711 -103.212484976607 -14.5966748971006 165.793629772287 +648.55 -14.6263165162957 165.023279265704 -0.299213662162036 -103.995684069653 -0.299213662162037 -103.995684069653 -14.6263165162957 165.023279265704 +653.5 -14.6576891928577 164.253035065346 -0.298989087467411 -104.779096796252 -0.298989087467411 -104.779096796252 -14.6576891928577 164.253035065346 +658.45 -14.6908068484823 163.482895669315 -0.298711993732648 -105.562732613833 -0.298711993732648 -105.562732613833 -14.6908068484823 163.482895669315 +663.4 -14.7256842867191 162.712860115755 -0.298383332150194 -106.346600818163 -0.298383332150194 -106.346600818163 -14.7256842867191 162.712860115755 +668.35 -14.7623372161383 161.942928009064 -0.298004091724872 -107.130710536636 -0.298004091724873 -107.130710536636 -14.7623372161383 161.942928009064 +673.3 -14.800782275249 161.173099547357 -0.297575298634526 -107.91507072163 -0.297575298634528 -107.91507072163 -14.800782275249 161.173099547357 +678.25 -14.8410370592669 160.403375551292 -0.297098015564591 -108.699690143969 -0.297098015564593 -108.699690143969 -14.8410370592669 160.403375551292 +683.2 -14.8831201488392 159.633757494353 -0.296573341016818 -109.484577386465 -0.296573341016817 -109.484577386465 -14.8831201488392 159.633757494353 +688.15 -14.9270511408421 158.864247534723 -0.296002408592193 -110.269740837565 -0.296002408592193 -110.269740837565 -14.9270511408421 158.864247534723 +693.1 -14.9728506813769 158.09484854887 -0.295386386248299 -111.055188685097 -0.295386386248299 -111.055188685097 -14.9728506813769 158.09484854887 +698.05 -15.0205405011035 157.325564166989 -0.294726475531207 -111.840928910123 -0.294726475531207 -111.840928910123 -15.0205405011035 157.325564166989 +703 -15.0701434530608 156.556398810455 -0.294023910782144 -112.626969280903 -0.294023910782145 -112.626969280903 -15.0701434530608 156.556398810455 +707.95 -15.1216835531372 155.787357731472 -0.293279958319086 -113.413317346967 -0.293279958319085 -113.413317346967 -15.1216835531372 155.787357731472 +712.9 -15.1751860233687 155.018447055084 -0.292495915593541 -114.19998043332 -0.292495915593543 -114.19998043332 -15.1751860233687 155.018447055084 +717.85 -15.2306773382605 154.249673823785 -0.29167311032272 -114.986965634759 -0.29167311032272 -114.986965634759 -15.2306773382605 154.249673823786 +722.8 -15.2881852743406 153.481046044935 -0.290812899597384 -115.77427981032 -0.290812899597386 -115.77427981032 -15.2881852743406 153.481046044934 +727.75 -15.3477389631779 152.712572741245 -0.28991666896563 -116.561929577868 -0.28991666896563 -116.561929577868 -15.3477389631779 152.712572741245 +732.7 -15.4093689481169 151.94426400462 -0.288985831492896 -117.349921308811 -0.288985831492895 -117.349921308811 -15.4093689481169 151.94426400462 +737.65 -15.473107245002 151.176131053652 -0.288021826798565 -118.138261122967 -0.288021826798565 -118.138261122967 -15.473107245002 151.176131053652 +742.6 -15.5389874071943 150.408186295121 -0.287026120069445 -118.926954883579 -0.287026120069447 -118.926954883579 -15.5389874071944 150.408186295121 +747.55 -15.6070445952081 149.640443389874 -0.286000201050545 -119.716008192471 -0.286000201050547 -119.716008192471 -15.6070445952081 149.640443389874 +752.5 -15.677315651328 148.872917323512 -0.284945583013515 -120.505426385376 -0.284945583013515 -120.505426385376 -15.677315651328 148.872917323512 +757.45 -15.7498391796016 148.105624482349 -0.283863801703196 -121.295214527418 -0.283863801703197 -121.295214527418 -15.7498391796016 148.105624482349 +762.4 -15.8246556316402 147.338582735158 -0.282756414262716 -122.085377408761 -0.282756414262717 -122.085377408761 -15.8246556316402 147.338582735158 +767.35 -15.9018073987045 146.571811521298 -0.281624998137592 -122.875919540436 -0.281624998137593 -122.875919540436 -15.9018073987045 146.571811521298 +772.3 -15.9813389105966 145.805331945855 -0.280471149959399 -123.666845150336 -0.2804711499594 -123.666845150335 -15.9813389105966 145.805331945855 +777.25 -16.0632967419361 145.039166882518 -0.279296484409475 -124.458158179399 -0.279296484409475 -124.458158179399 -16.0632967419361 145.039166882518 +782.2 -16.1477297264541 144.273341085012 -0.278102633063294 -125.249862277981 -0.278102633063296 -125.249862277981 -16.1477297264541 144.273341085012 +787.15 -16.2346890800053 143.507881307974 -0.276891243216042 -126.041960802411 -0.276891243216042 -126.041960802411 -16.2346890800053 143.507881307974 +792.1 -16.3242285330727 142.742816438295 -0.275663976690073 -126.834456811747 -0.275663976690073 -126.834456811747 -16.3242285330727 142.742816438295 +797.05 -16.416404473622 141.97817763806 -0.2744225086249 -127.62735306474 -0.274422508624901 -127.62735306474 -16.416404473622 141.97817763806 +802 -16.5112761012522 141.213998500375 -0.273168526250375 -128.420652016991 -0.273168526250375 -128.420652016991 -16.5112761012522 141.213998500375 +806.95 -16.608905593699 140.45031521951 -0.271903727643835 -129.214355818328 -0.271903727643834 -129.214355818328 -16.608905593699 140.45031521951 +811.9 -16.7093582868577 139.687166777005 -0.27062982047194 -130.008466310395 -0.270629820471942 -130.008466310395 -16.7093582868577 139.687166777005 +816.85 -16.8127028696308 138.924595145563 -0.269348520718017 -130.802985024459 -0.269348520718016 -130.802985024459 -16.8127028696308 138.924595145563 +821.8 -16.91901159505 138.16264551283 -0.268061551395706 -131.597913179448 -0.268061551395707 -131.597913179448 -16.91901159505 138.16264551283 +826.75 -17.028360509297 137.401366527428 -0.266770641249801 -132.393251680203 -0.266770641249801 -132.393251680203 -17.0283605092969 137.401366527428 +831.7 -17.1408297004332 136.640810569937 -0.265477523445156 -133.189001115981 -0.265477523445156 -133.189001115981 -17.1408297004332 136.640810569937 +836.65 -17.2565035688756 135.881034051898 -0.264183934244575 -133.985161759177 -0.264183934244572 -133.985161759177 -17.2565035688756 135.881034051898 +841.6 -17.375471121896 135.122097746353 -0.262891611676678 -134.781733564298 -0.262891611676677 -134.781733564298 -17.375471121896 135.122097746353 +846.55 -17.4978262947148 134.364067153934 -0.261602294194693 -135.578716167175 -0.261602294194692 -135.578716167175 -17.4978262947148 134.364067153934 +851.5 -17.6236683010791 133.607012909123 -0.260317719327228 -136.376108884424 -0.260317719327225 -136.376108884424 -17.623668301079 133.607012909123 +856.45 -17.7531020165949 132.851011231979 -0.259039622322037 -137.17391071316 -0.259039622322037 -137.17391071316 -17.7531020165949 132.851011231979 +861.4 -17.8862383985104 132.096144431458 -0.257769734783917 -137.97212033096 -0.257769734783916 -137.97212033096 -17.8862383985104 132.096144431458 +866.35 -18.0231949461439 131.342501467395 -0.256509783307828 -138.77073609609 -0.25650978330783 -138.77073609609 -18.0231949461439 131.342501467395 +871.3 -18.1640962067266 130.59017857935 -0.255261488108338 -139.569756047983 -0.25526148810834 -139.569756047983 -18.1640962067266 130.59017857935 +876.25 -18.3090743320928 129.839279991836 -0.254026561646641 -140.369177907987 -0.254026561646642 -140.369177907987 -18.3090743320928 129.839279991836 +881.2 -18.458269692424 129.089918707059 -0.252806707256306 -141.168999080373 -0.252806707256308 -141.168999080373 -18.458269692424 129.089918707059 +886.15 -18.6118315541585 128.342217398125 -0.251603617768955 -141.969216653612 -0.251603617768956 -141.969216653612 -18.6118315541585 128.342217398125 +891.1 -18.769918830225 127.596309417977 -0.250418974141193 -142.769827401916 -0.250418974141194 -142.769827401916 -18.769918830225 127.596309417977 +896.05 -18.9327009120032 126.852339941946 -0.249254444083988 -143.570827787056 -0.249254444083989 -143.570827787056 -18.9327009120032 126.852339941946 +901 -19.1003585938619 126.110467265097 -0.248111680695843 -144.372213960442 -0.248111680695846 -144.372213960442 -19.1003585938619 126.110467265097 +905.95 -19.2730851028455 125.370864279403 -0.24699232110108 -145.173981765485 -0.246992321101081 -145.173981765485 -19.2730851028455 125.370864279402 +910.9 -19.4510872481135 124.63372016055 -0.2458979850945 -145.976126740218 -0.245897985094502 -145.976126740218 -19.4510872481135 124.63372016055 +915.85 -19.6345867071513 123.89924229992 -0.244830273793877 -146.778644120205 -0.244830273793877 -146.778644120205 -19.6345867071513 123.89924229992 +920.8 -19.8238214686609 123.167658524357 -0.243790768301564 -147.581528841711 -0.243790768301565 -147.581528841711 -19.8238214686609 123.167658524357 +925.75 -20.0190474554901 122.43921965497 -0.242781028376624 -148.384775545148 -0.242781028376624 -148.384775545148 -20.0190474554901 122.43921965497 +930.7 -20.2205403551203 121.714202466927 -0.241802591118878 -149.188378578797 -0.241802591118877 -149.188378578797 -20.2205403551203 121.714202466927 +935.65 -20.4285976902505 120.992913125442 -0.240856969666242 -149.992332002788 -0.240856969666242 -149.992332002788 -20.4285976902505 120.992913125442 +940.6 -20.6435411681099 120.275691189693 -0.239945651906802 -150.796629593368 -0.239945651906801 -150.796629593368 -20.6435411681099 120.275691189693 +945.55 -20.8657193545612 119.562914297164 -0.239070099206971 -151.601264847415 -0.239070099206969 -151.601264847415 -20.8657193545612 119.562914297164 +950.5 -21.0955107281537 118.855003667024 -0.238231745157192 -152.406230987236 -0.238231745157191 -152.406230987236 -21.0955107281536 118.855003667024 +955.45 -21.3333271804946 118.152430594406 -0.237431994336578 -153.211520965614 -0.237431994336578 -153.211520965614 -21.3333271804946 118.152430594406 +960.4 -21.5796180431828 117.455724149801 -0.236672221097871 -154.017127471124 -0.236672221097872 -154.017127471124 -21.5796180431829 117.455724149801 +965.35 -21.8348747388229 116.765480352355 -0.235953768374142 -154.823042933695 -0.235953768374141 -154.823042933695 -21.8348747388229 116.765480352355 +970.3 -22.0996361752864 116.082373156485 -0.235277946508616 -155.629259530442 -0.235277946508614 -155.629259530442 -22.0996361752864 116.082373156485 +975.25 -22.3744950296561 115.40716768344 -0.234646032109009 -156.435769191724 -0.234646032109007 -156.435769191724 -22.374495029656 115.40716768344 +980.2 -22.660105102923 114.740736250761 -0.234059266927705 -157.242563607468 -0.234059266927704 -157.242563607468 -22.660105102923 114.740736250761 +985.15 -22.9571899707246 114.08407791363 -0.233518856769174 -158.049634233719 -0.233518856769173 -158.049634233719 -22.9571899707247 114.08407791363 +990.1 -23.2665532123679 113.438342447742 -0.233025970425903 -158.85697229943 -0.233025970425903 -158.85697229943 -23.2665532123679 113.438342447742 +995.05 -23.5890905742609 112.804859995029 -0.232581738644205 -159.664568813481 -0.232581738644202 -159.664568813481 -23.5890905742609 112.80485999503 +1000 -23.92580452052 112.185177992112 -0.23218725312111 -160.472414571918 -0.232187253121107 -160.472414571918 -23.92580452052 112.185177992112 diff --git a/skrf/media/tests/test_definedaeptandz0.py b/skrf/media/tests/test_definedaeptandz0.py new file mode 100644 index 000000000..1640c0ca7 --- /dev/null +++ b/skrf/media/tests/test_definedaeptandz0.py @@ -0,0 +1,112 @@ +import os +import unittest + +import numpy as np + +import skrf as rf +from skrf.media import DefinedAEpTandZ0 + +try: + from matplotlib import pyplot as plt + rf.stylely() +except ImportError: + pass + + + +class DefinedAEpTandZ0TestCase(unittest.TestCase): + """ + Testcase for the DefinedAEpTandZ0 Media + """ + def setUp(self): + """ + Read in all the network data required for tests + """ + self.data_dir_awr = os.path.dirname(os.path.abspath(__file__)) + \ + '/awr/' + + self.ref_awr = [ + {'color': 'k', + 'n': rf.Network(os.path.join(self.data_dir_awr, + 'tlinp.s2p'))}, + ] + + # default parameter set for tests + self.verbose = False # output comparison plots if True + self.Zn = 41.0635803351402 + self.A = 0.0670188722315605 + self.l = 74.2415154883262e-3 + self.ep_r = 3.25354286428265 + self.tand = 0.0133936758939493 + + + def test_line_awr(self): + """ + Test against the AWR results + """ + if self.verbose: + fig, axs = plt.subplots(2, 2, figsize = (8,6)) + fig.suptitle('awr/skrf') + fig2, axs2 = plt.subplots(2, 2, figsize = (8,6)) + fig2.suptitle('awr/skrf residuals') + + limit_db = 1e-5 + limit_deg = 1e-4 + + for ref in self.ref_awr: + m = DefinedAEpTandZ0( + frequency = ref['n'].frequency, + z0 = self.Zn, + ep_r = self.ep_r, + A = self.A, + f_A = ref['n'].frequency.f[0], + tanD = self.tand, + z0_port = 50 + ) + line = m.line(d=self.l, unit='m') + line.name = 'skrf,awr' + + # residuals + res = line / ref['n'] + res.name = 'residuals ' + ref['n'].name + + # test if within limit lines + self.assertTrue( + np.all(np.abs(res.s_db[:, 0, 0]) < limit_db)) + self.assertTrue( + np.all(np.abs(res.s_deg[:, 0, 0]) < limit_deg)) + self.assertTrue(np.all(np.abs(res.s_db[:, 1, 0]) < limit_db)) + self.assertTrue(np.all(np.abs(res.s_deg[:, 1, 0]) < limit_deg)) + + if self.verbose: + line.plot_s_db(0, 0, ax = axs[0, 0], color = ref['color'], + linestyle = 'none', marker = 'x') + ref['n'].plot_s_db(0, 0, ax = axs[0, 0], color = ref['color']) + res.plot_s_db(0, 0, ax = axs2[0, 0], linestyle = 'dashed', + color = ref['color']) + + line.plot_s_deg(0, 0, ax = axs[0, 1], color = ref['color'], + linestyle = 'none', marker = 'x') + ref['n'].plot_s_deg(0, 0, ax = axs[0, 1], color = ref['color']) + res.plot_s_deg(0, 0, ax = axs2[0, 1], linestyle = 'dashed', + color = ref['color']) + + line.plot_s_db(1, 0, ax = axs[1, 0], color = ref['color'], + linestyle = 'none', marker = 'x') + ref['n'].plot_s_db(1, 0, ax = axs[1, 0], color = ref['color']) + res.plot_s_db(1, 0, ax = axs2[1, 0], linestyle = 'dashed', + color = ref['color']) + + line.plot_s_deg(1, 0, ax = axs[1, 1], color = ref['color'], + linestyle = 'none', marker = 'x') + ref['n'].plot_s_deg(1, 0, ax = axs[1, 1], color = ref['color']) + res.plot_s_deg(1, 0, ax = axs2[1, 1], linestyle = 'dashed', + color = ref['color']) + + + if self.verbose: + fig.tight_layout() + fig2.tight_layout() + +if __name__ == '__main__': + unittest.main() From 549f3c2e59bd41a204cd8b5883b9df8927e5ede5 Mon Sep 17 00:00:00 2001 From: mhuser Date: Wed, 13 Nov 2024 11:54:20 +0100 Subject: [PATCH 26/58] fix editor must remove trailing spaces --- skrf/media/tests/test_definedaeptandz0.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/skrf/media/tests/test_definedaeptandz0.py b/skrf/media/tests/test_definedaeptandz0.py index 1640c0ca7..999f4d669 100644 --- a/skrf/media/tests/test_definedaeptandz0.py +++ b/skrf/media/tests/test_definedaeptandz0.py @@ -107,6 +107,6 @@ def test_line_awr(self): if self.verbose: fig.tight_layout() fig2.tight_layout() - + if __name__ == '__main__': unittest.main() From 5f565a37680ffa6ad46b093cc588b4ccbd960fea Mon Sep 17 00:00:00 2001 From: mhuser Date: Wed, 13 Nov 2024 21:48:57 +0100 Subject: [PATCH 27/58] keep z0 parameter as raw characteristic impedance if array-llike --- skrf/media/definedAEpTandZ0.py | 32 ++++++++++------ skrf/media/tests/test_definedaeptandz0.py | 46 +++++++++++++++++++++++ 2 files changed, 66 insertions(+), 12 deletions(-) diff --git a/skrf/media/definedAEpTandZ0.py b/skrf/media/definedAEpTandZ0.py index 9f096e8ad..6e04f2b00 100644 --- a/skrf/media/definedAEpTandZ0.py +++ b/skrf/media/definedAEpTandZ0.py @@ -18,8 +18,9 @@ from __future__ import annotations import warnings +from typing import Sequence -from numpy import imag, log, ones, pi, real, sqrt +from numpy import imag, log, ndarray, ones, pi, real, sqrt from scipy.constants import c from ..constants import NumberLike @@ -87,10 +88,12 @@ class DefinedAEpTandZ0(Media): Dielectric relative permittivity loss tangent :math:`\tan\delta`. See `ep_r`. z0 : number, array-like, default 50.0 Quasi-static nominal impedance of the medium. - Because of the distortion introduced by the conductor and dielectric + Because of the dispersion introduced by the conductor and dielectric losses, the characteristic impedance can be frequency-dependent with an imaginary part. The characteristic impedance is computed with an RLGC model. + If the impedance parameter is array-like, the characteristic impedance + is assigned to this value without modification. .. math:: @@ -166,19 +169,24 @@ def __init__(self, frequency: Frequency| None = None, Media.__init__(self, frequency=frequency, z0_port=z0_port) self.A, self.f_A = A, f_A self.ep_r, self.tanD = ep_r, tanD - self.Zn = z0 + self.f_low, self.f_high, self.f_ep = f_low, f_high, f_ep self.model = model - # z0 is the nominal impedance. Compute characteristic impedance with - # an RLGC model based on alpha conductor and alpha dielectric. - self.R = 2 * self.Zn * self.alpha_conductor - self.L = self.Zn * sqrt(self.ep_r) / c - self.G = 2 / self.Zn * self.alpha_dielectric - self.C = sqrt(ep_r) / c / self.Zn - self.z0_characteristic = sqrt( - (self.R + 1j * self.frequency.w * self.L) / - (self.G + 1j * self.frequency.w * self.C)) + if isinstance(z0, (Sequence, ndarray)): + # keep raw impedance for characteristic impedance + self.z0_characteristic = z0 + else: + # z0 is the nominal impedance. Compute characteristic impedance with + # an RLGC model based on alpha conductor and alpha dielectric. + self.Zn = z0 + self.R = 2 * self.Zn * self.alpha_conductor + self.L = self.Zn * sqrt(self.ep_r) / c + self.G = 2 / self.Zn * self.alpha_dielectric + self.C = sqrt(ep_r) / c / self.Zn + self.z0_characteristic = sqrt( + (self.R + 1j * self.frequency.w * self.L) / + (self.G + 1j * self.frequency.w * self.C)) if Z0 is not None: # warns of deprecation diff --git a/skrf/media/tests/test_definedaeptandz0.py b/skrf/media/tests/test_definedaeptandz0.py index 999f4d669..69004699c 100644 --- a/skrf/media/tests/test_definedaeptandz0.py +++ b/skrf/media/tests/test_definedaeptandz0.py @@ -108,5 +108,51 @@ def test_line_awr(self): fig.tight_layout() fig2.tight_layout() + def test_raw_z0_array(self): + """ + Test if passing array-like value to z0 results to this raw value being + assigned to characteristic impedance. + """ + freq = rf.Frequency(1, 3, 3, 'GHz') + # nominal impedance and dispersion + z1 = 75 + m1 = DefinedAEpTandZ0( + frequency = freq, + z0 = z1, + ep_r = self.ep_r, + A = self.A, + f_A = freq.f[0], + tanD = self.tand, + z0_port = 50 + ) + # z0_characteristic complex and frequency dependant + self.assertTrue(np.all(np.abs(np.imag(m1.z0_characteristic) \ + > 1j * 1e-1))) + self.assertTrue(m1.z0_characteristic[0] != m1.z0_characteristic[-1]) + # Sequence are kept as raw z0_characteristic + z2 = [45, 50, 55] + m2 = DefinedAEpTandZ0( + frequency = freq, + z0 = z2, + ep_r = self.ep_r, + A = self.A, + f_A = freq.f[0], + tanD = self.tand, + z0_port = 50 + ) + self.assertTrue(np.allclose(z2, m2.z0_characteristic)) + # Numpy arrays are kept as raw z0_characteristic + z3 = np.array([48, 50, 52]) + m3 = DefinedAEpTandZ0( + frequency = freq, + z0 = z3, + ep_r = self.ep_r, + A = self.A, + f_A = freq.f[0], + tanD = self.tand, + z0_port = 50 + ) + self.assertTrue(np.allclose(z3, m3.z0_characteristic)) + if __name__ == '__main__': unittest.main() From cca73ce76d5aca9210fc3964a87ddaa596e28ed5 Mon Sep 17 00:00:00 2001 From: mhuser Date: Thu, 14 Nov 2024 22:19:22 +0100 Subject: [PATCH 28/58] compare np.imag results with real number and test single-element array --- skrf/media/tests/test_definedaeptandz0.py | 45 ++++++++++++++++------- 1 file changed, 32 insertions(+), 13 deletions(-) diff --git a/skrf/media/tests/test_definedaeptandz0.py b/skrf/media/tests/test_definedaeptandz0.py index 69004699c..5a778606f 100644 --- a/skrf/media/tests/test_definedaeptandz0.py +++ b/skrf/media/tests/test_definedaeptandz0.py @@ -38,6 +38,8 @@ def setUp(self): self.l = 74.2415154883262e-3 self.ep_r = 3.25354286428265 self.tand = 0.0133936758939493 + # frequency vector with only 3 points + self.freq = rf.Frequency(1, 3, 3, 'GHz') def test_line_awr(self): @@ -108,35 +110,52 @@ def test_line_awr(self): fig.tight_layout() fig2.tight_layout() + def test_nominal_impedance_dispersion(self): + """ + Test if passing scalar value to z0 results to this nominal impedance + being dispersed to a complex frequency-dependent characteristic + impedance. + """ + Zn = 75 + m = DefinedAEpTandZ0( + frequency = self.freq, + z0 = Zn, + ep_r = self.ep_r, + A = self.A, + f_A = self.freq.f[0], + tanD = self.tand, + z0_port = 50 + ) + # z0_characteristic complex and frequency dependant + self.assertTrue(np.all(np.abs(np.imag(m.z0_characteristic) > 1e-1))) + self.assertTrue(m.z0_characteristic[0] != m.z0_characteristic[-1]) + def test_raw_z0_array(self): """ Test if passing array-like value to z0 results to this raw value being assigned to characteristic impedance. """ - freq = rf.Frequency(1, 3, 3, 'GHz') - # nominal impedance and dispersion - z1 = 75 + + # Single-element array are kept as raw z0_characteristic + z1 = [75] m1 = DefinedAEpTandZ0( - frequency = freq, + frequency = self.freq, z0 = z1, ep_r = self.ep_r, A = self.A, - f_A = freq.f[0], + f_A = self.freq.f[0], tanD = self.tand, z0_port = 50 ) - # z0_characteristic complex and frequency dependant - self.assertTrue(np.all(np.abs(np.imag(m1.z0_characteristic) \ - > 1j * 1e-1))) - self.assertTrue(m1.z0_characteristic[0] != m1.z0_characteristic[-1]) + self.assertTrue(np.allclose(z1 * np.ones(3), m1.z0_characteristic)) # Sequence are kept as raw z0_characteristic z2 = [45, 50, 55] m2 = DefinedAEpTandZ0( - frequency = freq, + frequency = self.freq, z0 = z2, ep_r = self.ep_r, A = self.A, - f_A = freq.f[0], + f_A = self.freq.f[0], tanD = self.tand, z0_port = 50 ) @@ -144,11 +163,11 @@ def test_raw_z0_array(self): # Numpy arrays are kept as raw z0_characteristic z3 = np.array([48, 50, 52]) m3 = DefinedAEpTandZ0( - frequency = freq, + frequency = self.freq, z0 = z3, ep_r = self.ep_r, A = self.A, - f_A = freq.f[0], + f_A = self.freq.f[0], tanD = self.tand, z0_port = 50 ) From db7972ddfcd9fdc3916c14bdc5a7d5af2a009288 Mon Sep 17 00:00:00 2001 From: Lansus Date: Fri, 15 Nov 2024 22:07:38 +0800 Subject: [PATCH 29/58] Use s-parameter to build lumped components. --- skrf/media/media.py | 57 +++++++++++++++++++++++++++++++-------------- 1 file changed, 39 insertions(+), 18 deletions(-) diff --git a/skrf/media/media.py b/skrf/media/media.py index 33ef32f0b..86251285a 100644 --- a/skrf/media/media.py +++ b/skrf/media/media.py @@ -644,13 +644,20 @@ def resistor(self, R: NumberLike, *args, **kwargs) -> Network: inductor """ result = self.match(nports=2, **kwargs) - y = np.zeros(shape=result.s.shape, dtype=complex) + s = np.zeros(shape=result.s.shape, dtype=complex) R = np.array(R) - y[:, 0, 0] = 1.0 / R - y[:, 1, 1] = 1.0 / R - y[:, 0, 1] = -1.0 / R - y[:, 1, 0] = -1.0 / R - result.y = y + # Convert Y-parameter resistor to S parameters to accommodate any R value. + # y[:, 0, 0] = 1.0 / R + # y[:, 1, 1] = 1.0 / R + # y[:, 0, 1] = -1.0 / R + # y[:, 1, 0] = -1.0 / R + z0_0, z0_1 = result.z0[:, 0], result.z0[:, 1] + temp = R + (z0_0 + z0_1) + s[:, 0, 0] = (R - z0_0 + z0_1) / temp + s[:, 1, 1] = (R + z0_0 - z0_1) / temp + s[:, 0, 1] = 2 * (z0_0 * z0_1)**0.5 / temp + s[:, 1, 0] = 2 * (z0_0 * z0_1)**0.5 / temp + result.s = s return result def capacitor(self, C: NumberLike, **kwargs) -> Network: @@ -681,13 +688,20 @@ def capacitor(self, C: NumberLike, **kwargs) -> Network: """ result = self.match(nports=2, **kwargs) w = self.frequency.w - y = np.zeros(shape=result.s.shape, dtype=complex) + s = np.zeros(shape=result.s.shape, dtype=complex) C = np.array(C) - y[:, 0, 0] = 1j * w * C - y[:, 1, 1] = 1j * w * C - y[:, 0, 1] = -1j * w * C - y[:, 1, 0] = -1j * w * C - result.y = y + # Convert Y-parameter capacitor to S parameters to accommodate any C value. + # y[:, 0, 0] = 1j * w * C + # y[:, 1, 1] = 1j * w * C + # y[:, 0, 1] = -1j * w * C + # y[:, 1, 0] = -1j * w * C + z0_0, z0_1 = result.z0[:, 0], result.z0[:, 1] + temp = 1.0 + 1j * w * C * (z0_0 + z0_1) + s[:, 0, 0] = (1.0 - 1j * w * C * (z0_0 - z0_1) ) / temp + s[:, 1, 1] = (1.0 - 1j * w * C * (z0_1 - z0_0) ) / temp + s[:, 0, 1] = (2j * w * C * (z0_0 * z0_1)**0.5) / temp + s[:, 1, 0] = (2j * w * C * (z0_0 * z0_1)**0.5) / temp + result.s = s return result def inductor(self, L: NumberLike, **kwargs) -> Network: @@ -718,13 +732,20 @@ def inductor(self, L: NumberLike, **kwargs) -> Network: """ result = self.match(nports=2, **kwargs) w = self.frequency.w - y = np.zeros(shape=result.s.shape, dtype=complex) + s = np.zeros(shape=result.s.shape, dtype=complex) L = np.array(L) - y[:, 0, 0] = 1.0 / (1j * w * L) - y[:, 1, 1] = 1.0 / (1j * w * L) - y[:, 0, 1] = -1.0 / (1j * w * L) - y[:, 1, 0] = -1.0 / (1j * w * L) - result.y = y + # Convert Y-parameter inductor to S parameters to accommodate any L value. + # y[:, 0, 0] = 1.0 / (1j * w * L) + # y[:, 1, 1] = 1.0 / (1j * w * L) + # y[:, 0, 1] = -1.0 / (1j * w * L) + # y[:, 1, 0] = -1.0 / (1j * w * L) + z0_0, z0_1 = result.z0[:, 0], result.z0[:, 1] + temp = (1j * w * L) + (z0_0 + z0_1) + s[:, 0, 0] = (1j * w * L - z0_0 + z0_1) / temp + s[:, 1, 1] = (1j * w * L + z0_0 - z0_1) / temp + s[:, 0, 1] = 2 * (z0_0 * z0_1)**0.5 / temp + s[:, 1, 0] = 2 * (z0_0 * z0_1)**0.5 / temp + result.s = s return result def impedance_mismatch(self, z1: NumberLike, z2: NumberLike, **kwargs) -> Network: From 5438af8f408082b8d69390a0e2b0c4cadd8115c0 Mon Sep 17 00:00:00 2001 From: Lansus Date: Fri, 15 Nov 2024 22:25:20 +0800 Subject: [PATCH 30/58] Update unit test. --- skrf/media/tests/test_media.py | 39 ++++++++++++++++++++++++++++++++++ 1 file changed, 39 insertions(+) diff --git a/skrf/media/tests/test_media.py b/skrf/media/tests/test_media.py index 4e738812b..6bdb41368 100644 --- a/skrf/media/tests/test_media.py +++ b/skrf/media/tests/test_media.py @@ -182,6 +182,19 @@ def test_resistor(self): ABCD[:,0,1] = Z assert_array_almost_equal(ABCD, ntwk.a) + def test_resistor_mismatch(self): + """ + Compare the component values against s-parameters generated by QUCS. + The mismatch QUCS component is generated by renormalize() method. + """ + name, mismatch_z0 = 'resistor,1ohm', [40, 50] + qucs_ntwk = Network(os.path.join(self.files_dir, name + '.s2p')) + qucs_ntwk.renormalize(mismatch_z0) + self.dummy_media.frequency = qucs_ntwk.frequency + skrf_ntwk = self.dummy_media.resistor(1, name = name, z0=mismatch_z0) + self.assertEqual(qucs_ntwk, skrf_ntwk) + self.assertEqual(qucs_ntwk.name, skrf_ntwk.name) + def test_shunt_resistor(self): """ Test the naming of the network. When circuit is used to connect a @@ -218,6 +231,19 @@ def test_capacitor(self): ABCD[:,0,1] = Z assert_array_almost_equal(ABCD, ntwk.a) + def test_capacitor_mismatch(self): + """ + Compare the component values against s-parameters generated by QUCS. + The mismatch QUCS component is generated by renormalize() method. + """ + name, mismatch_z0 = 'capacitor,p01pF', [40, 50] + qucs_ntwk = Network(os.path.join(self.files_dir, name + '.s2p')) + qucs_ntwk.renormalize(mismatch_z0) + self.dummy_media.frequency = qucs_ntwk.frequency + skrf_ntwk = self.dummy_media.capacitor(.01e-12, name = name, z0=mismatch_z0) + self.assertEqual(qucs_ntwk, skrf_ntwk) + self.assertEqual(qucs_ntwk.name, skrf_ntwk.name) + def test_shunt_capacitor(self): """ Test the naming of the network. When circuit is used to connect a @@ -285,6 +311,19 @@ def test_inductor(self): ABCD[:,0,1] = Z assert_array_almost_equal(ABCD, ntwk.a) + def test_inductor_mismatch(self): + """ + Compare the component values against s-parameters generated by QUCS. + The mismatch QUCS component is generated by renormalize() method. + """ + name, mismatch_z0 = 'inductor,p1nH', [40, 50] + qucs_ntwk = Network(os.path.join(self.files_dir, name + '.s2p')) + qucs_ntwk.renormalize(mismatch_z0) + self.dummy_media.frequency = qucs_ntwk.frequency + skrf_ntwk = self.dummy_media.inductor(.1e-9, name = name, z0=mismatch_z0) + self.assertEqual(qucs_ntwk, skrf_ntwk) + self.assertEqual(qucs_ntwk.name, skrf_ntwk.name) + def test_shunt_inductor(self): """ Test the naming of the network. When circuit is used to connect a From 0a7ce2d7adc7cde5d483e9d860f83c8966ff14de Mon Sep 17 00:00:00 2001 From: Asachoo Date: Sat, 16 Nov 2024 01:42:34 +0800 Subject: [PATCH 31/58] support for complex port impedance. --- skrf/media/media.py | 24 ++++++++--------- skrf/media/tests/test_media.py | 48 +++++++++++++++++++++++----------- 2 files changed, 45 insertions(+), 27 deletions(-) diff --git a/skrf/media/media.py b/skrf/media/media.py index 86251285a..14dbd8b29 100644 --- a/skrf/media/media.py +++ b/skrf/media/media.py @@ -653,10 +653,10 @@ def resistor(self, R: NumberLike, *args, **kwargs) -> Network: # y[:, 1, 0] = -1.0 / R z0_0, z0_1 = result.z0[:, 0], result.z0[:, 1] temp = R + (z0_0 + z0_1) - s[:, 0, 0] = (R - z0_0 + z0_1) / temp - s[:, 1, 1] = (R + z0_0 - z0_1) / temp - s[:, 0, 1] = 2 * (z0_0 * z0_1)**0.5 / temp - s[:, 1, 0] = 2 * (z0_0 * z0_1)**0.5 / temp + s[:, 0, 0] = (R - z0_0.conj() + z0_1) / temp + s[:, 1, 1] = (R + z0_0 - z0_1.conj()) / temp + s[:, 0, 1] = 2 * (z0_0.real * z0_1.real)**0.5 / temp + s[:, 1, 0] = 2 * (z0_0.real * z0_1.real)**0.5 / temp result.s = s return result @@ -697,10 +697,10 @@ def capacitor(self, C: NumberLike, **kwargs) -> Network: # y[:, 1, 0] = -1j * w * C z0_0, z0_1 = result.z0[:, 0], result.z0[:, 1] temp = 1.0 + 1j * w * C * (z0_0 + z0_1) - s[:, 0, 0] = (1.0 - 1j * w * C * (z0_0 - z0_1) ) / temp - s[:, 1, 1] = (1.0 - 1j * w * C * (z0_1 - z0_0) ) / temp - s[:, 0, 1] = (2j * w * C * (z0_0 * z0_1)**0.5) / temp - s[:, 1, 0] = (2j * w * C * (z0_0 * z0_1)**0.5) / temp + s[:, 0, 0] = (1.0 - 1j * w * C * (z0_0.conj() - z0_1) ) / temp + s[:, 1, 1] = (1.0 - 1j * w * C * (z0_1.conj() - z0_0) ) / temp + s[:, 0, 1] = (2j * w * C * (z0_0.real * z0_1.real)**0.5) / temp + s[:, 1, 0] = (2j * w * C * (z0_0.real * z0_1.real)**0.5) / temp result.s = s return result @@ -741,10 +741,10 @@ def inductor(self, L: NumberLike, **kwargs) -> Network: # y[:, 1, 0] = -1.0 / (1j * w * L) z0_0, z0_1 = result.z0[:, 0], result.z0[:, 1] temp = (1j * w * L) + (z0_0 + z0_1) - s[:, 0, 0] = (1j * w * L - z0_0 + z0_1) / temp - s[:, 1, 1] = (1j * w * L + z0_0 - z0_1) / temp - s[:, 0, 1] = 2 * (z0_0 * z0_1)**0.5 / temp - s[:, 1, 0] = 2 * (z0_0 * z0_1)**0.5 / temp + s[:, 0, 0] = (1j * w * L - z0_0.conj() + z0_1) / temp + s[:, 1, 1] = (1j * w * L + z0_0 - z0_1.conj()) / temp + s[:, 0, 1] = 2 * (z0_0.real * z0_1.real)**0.5 / temp + s[:, 1, 0] = 2 * (z0_0.real * z0_1.real)**0.5 / temp result.s = s return result diff --git a/skrf/media/tests/test_media.py b/skrf/media/tests/test_media.py index 6bdb41368..e58f73fa1 100644 --- a/skrf/media/tests/test_media.py +++ b/skrf/media/tests/test_media.py @@ -187,13 +187,19 @@ def test_resistor_mismatch(self): Compare the component values against s-parameters generated by QUCS. The mismatch QUCS component is generated by renormalize() method. """ - name, mismatch_z0 = 'resistor,1ohm', [40, 50] + # Config the qucs_ntwk and media + name = 'resistor,1ohm' qucs_ntwk = Network(os.path.join(self.files_dir, name + '.s2p')) - qucs_ntwk.renormalize(mismatch_z0) self.dummy_media.frequency = qucs_ntwk.frequency - skrf_ntwk = self.dummy_media.resistor(1, name = name, z0=mismatch_z0) - self.assertEqual(qucs_ntwk, skrf_ntwk) - self.assertEqual(qucs_ntwk.name, skrf_ntwk.name) + + # Test for real and imag port impedance + mismatch_z0_tuple = ([25, 50], [25-5j, 50+10j], [25, 50-10j]) + for mismatch_z0 in mismatch_z0_tuple: + qucs_ntwk_copy = qucs_ntwk.copy() + qucs_ntwk_copy.renormalize(mismatch_z0) + skrf_ntwk = self.dummy_media.resistor(1, name = name, z0=mismatch_z0) + self.assertEqual(qucs_ntwk_copy, skrf_ntwk) + self.assertEqual(qucs_ntwk_copy.s_def, skrf_ntwk.s_def) def test_shunt_resistor(self): """ @@ -236,13 +242,19 @@ def test_capacitor_mismatch(self): Compare the component values against s-parameters generated by QUCS. The mismatch QUCS component is generated by renormalize() method. """ - name, mismatch_z0 = 'capacitor,p01pF', [40, 50] + # Config the qucs_ntwk and media + name = 'capacitor,p01pF' qucs_ntwk = Network(os.path.join(self.files_dir, name + '.s2p')) - qucs_ntwk.renormalize(mismatch_z0) self.dummy_media.frequency = qucs_ntwk.frequency - skrf_ntwk = self.dummy_media.capacitor(.01e-12, name = name, z0=mismatch_z0) - self.assertEqual(qucs_ntwk, skrf_ntwk) - self.assertEqual(qucs_ntwk.name, skrf_ntwk.name) + + # Test for real and imag port impedance + mismatch_z0_tuple = ([25, 50], [25-5j, 50+10j], [25, 50-10j]) + for mismatch_z0 in mismatch_z0_tuple: + qucs_ntwk_copy = qucs_ntwk.copy() + qucs_ntwk_copy.renormalize(mismatch_z0) + skrf_ntwk = self.dummy_media.capacitor(.01e-12, name = name, z0=mismatch_z0) + self.assertEqual(qucs_ntwk_copy, skrf_ntwk) + self.assertEqual(qucs_ntwk_copy.s_def, skrf_ntwk.s_def) def test_shunt_capacitor(self): """ @@ -316,13 +328,19 @@ def test_inductor_mismatch(self): Compare the component values against s-parameters generated by QUCS. The mismatch QUCS component is generated by renormalize() method. """ - name, mismatch_z0 = 'inductor,p1nH', [40, 50] + # Config the qucs_ntwk and media + name = 'inductor,p1nH' qucs_ntwk = Network(os.path.join(self.files_dir, name + '.s2p')) - qucs_ntwk.renormalize(mismatch_z0) self.dummy_media.frequency = qucs_ntwk.frequency - skrf_ntwk = self.dummy_media.inductor(.1e-9, name = name, z0=mismatch_z0) - self.assertEqual(qucs_ntwk, skrf_ntwk) - self.assertEqual(qucs_ntwk.name, skrf_ntwk.name) + + # Test for real and imag port impedance + mismatch_z0_tuple = ([25, 50], [25-5j, 50+10j], [25, 50-10j]) + for mismatch_z0 in mismatch_z0_tuple: + qucs_ntwk_copy = qucs_ntwk.copy() + qucs_ntwk_copy.renormalize(mismatch_z0) + skrf_ntwk = self.dummy_media.inductor(.1e-9, name = name, z0=mismatch_z0) + self.assertEqual(qucs_ntwk_copy, skrf_ntwk) + self.assertEqual(qucs_ntwk_copy.s_def, skrf_ntwk.s_def) def test_shunt_inductor(self): """ From 2d20bbaad6dd328e8b71b754c75362745cf9b6fe Mon Sep 17 00:00:00 2001 From: Asachoo Date: Sat, 16 Nov 2024 11:22:08 +0800 Subject: [PATCH 32/58] Support convert s_def by explicit specification. --- skrf/media/media.py | 60 +++++++++++++++++++++++++++++---------------- 1 file changed, 39 insertions(+), 21 deletions(-) diff --git a/skrf/media/media.py b/skrf/media/media.py index 14dbd8b29..4c90b9c1b 100644 --- a/skrf/media/media.py +++ b/skrf/media/media.py @@ -643,21 +643,27 @@ def resistor(self, R: NumberLike, *args, **kwargs) -> Network: capacitor inductor """ - result = self.match(nports=2, **kwargs) + s_def = kwargs.pop('s_def', S_DEF_DEFAULT) + result = self.match(nports=2, s_def=S_DEF_DEFAULT, **kwargs) s = np.zeros(shape=result.s.shape, dtype=complex) R = np.array(R) - # Convert Y-parameter resistor to S parameters to accommodate any R value. + # Convert Y-parameters resistor to S-parameters in power wave to accommodate any R value. # y[:, 0, 0] = 1.0 / R # y[:, 1, 1] = 1.0 / R # y[:, 0, 1] = -1.0 / R # y[:, 1, 0] = -1.0 / R z0_0, z0_1 = result.z0[:, 0], result.z0[:, 1] - temp = R + (z0_0 + z0_1) - s[:, 0, 0] = (R - z0_0.conj() + z0_1) / temp - s[:, 1, 1] = (R + z0_0 - z0_1.conj()) / temp - s[:, 0, 1] = 2 * (z0_0.real * z0_1.real)**0.5 / temp - s[:, 1, 0] = 2 * (z0_0.real * z0_1.real)**0.5 / temp + denom = R + (z0_0 + z0_1) + s[:, 0, 0] = (R - z0_0.conj() + z0_1) / denom + s[:, 1, 1] = (R + z0_0 - z0_1.conj()) / denom + s[:, 0, 1] = 2 * (z0_0.real * z0_1.real)**0.5 / denom + s[:, 1, 0] = 2 * (z0_0.real * z0_1.real)**0.5 / denom result.s = s + + # Renormalize into s_def if required + if s_def != S_DEF_DEFAULT: + result.renormalize(z_new=result.z0, s_def=s_def) + return result def capacitor(self, C: NumberLike, **kwargs) -> Network: @@ -686,22 +692,28 @@ def capacitor(self, C: NumberLike, **kwargs) -> Network: resistor inductor """ - result = self.match(nports=2, **kwargs) + s_def = kwargs.pop('s_def', S_DEF_DEFAULT) + result = self.match(nports=2, s_def=S_DEF_DEFAULT, **kwargs) w = self.frequency.w s = np.zeros(shape=result.s.shape, dtype=complex) C = np.array(C) - # Convert Y-parameter capacitor to S parameters to accommodate any C value. + # Convert Y-parameters capacitor to S-parameters in power wave to accommodate any C value. # y[:, 0, 0] = 1j * w * C # y[:, 1, 1] = 1j * w * C # y[:, 0, 1] = -1j * w * C # y[:, 1, 0] = -1j * w * C z0_0, z0_1 = result.z0[:, 0], result.z0[:, 1] - temp = 1.0 + 1j * w * C * (z0_0 + z0_1) - s[:, 0, 0] = (1.0 - 1j * w * C * (z0_0.conj() - z0_1) ) / temp - s[:, 1, 1] = (1.0 - 1j * w * C * (z0_1.conj() - z0_0) ) / temp - s[:, 0, 1] = (2j * w * C * (z0_0.real * z0_1.real)**0.5) / temp - s[:, 1, 0] = (2j * w * C * (z0_0.real * z0_1.real)**0.5) / temp + denom = 1.0 + 1j * w * C * (z0_0 + z0_1) + s[:, 0, 0] = (1.0 - 1j * w * C * (z0_0.conj() - z0_1) ) / denom + s[:, 1, 1] = (1.0 - 1j * w * C * (z0_1.conj() - z0_0) ) / denom + s[:, 0, 1] = (2j * w * C * (z0_0.real * z0_1.real)**0.5) / denom + s[:, 1, 0] = (2j * w * C * (z0_0.real * z0_1.real)**0.5) / denom result.s = s + + # Renormalize into s_def if required + if s_def != S_DEF_DEFAULT: + result.renormalize(z_new=result.z0, s_def=s_def) + return result def inductor(self, L: NumberLike, **kwargs) -> Network: @@ -730,22 +742,28 @@ def inductor(self, L: NumberLike, **kwargs) -> Network: capacitor resistor """ - result = self.match(nports=2, **kwargs) + s_def = kwargs.pop('s_def', S_DEF_DEFAULT) + result = self.match(nports=2, s_def=S_DEF_DEFAULT, **kwargs) w = self.frequency.w s = np.zeros(shape=result.s.shape, dtype=complex) L = np.array(L) - # Convert Y-parameter inductor to S parameters to accommodate any L value. + # Convert Y-parameters inductor to S-parameters in power wave to accommodate any L value. # y[:, 0, 0] = 1.0 / (1j * w * L) # y[:, 1, 1] = 1.0 / (1j * w * L) # y[:, 0, 1] = -1.0 / (1j * w * L) # y[:, 1, 0] = -1.0 / (1j * w * L) z0_0, z0_1 = result.z0[:, 0], result.z0[:, 1] - temp = (1j * w * L) + (z0_0 + z0_1) - s[:, 0, 0] = (1j * w * L - z0_0.conj() + z0_1) / temp - s[:, 1, 1] = (1j * w * L + z0_0 - z0_1.conj()) / temp - s[:, 0, 1] = 2 * (z0_0.real * z0_1.real)**0.5 / temp - s[:, 1, 0] = 2 * (z0_0.real * z0_1.real)**0.5 / temp + denom = (1j * w * L) + (z0_0 + z0_1) + s[:, 0, 0] = (1j * w * L - z0_0.conj() + z0_1) / denom + s[:, 1, 1] = (1j * w * L + z0_0 - z0_1.conj()) / denom + s[:, 0, 1] = 2 * (z0_0.real * z0_1.real)**0.5 / denom + s[:, 1, 0] = 2 * (z0_0.real * z0_1.real)**0.5 / denom result.s = s + + # Renormalize into s_def if required + if s_def != S_DEF_DEFAULT: + result.renormalize(z_new=result.z0, s_def=s_def) + return result def impedance_mismatch(self, z1: NumberLike, z2: NumberLike, **kwargs) -> Network: From 5daa41415f431333f45c0adf7177f3b61dde5636 Mon Sep 17 00:00:00 2001 From: Asachoo Date: Sat, 16 Nov 2024 11:23:17 +0800 Subject: [PATCH 33/58] Added Y parameter definition detection for s-parameters. --- skrf/media/tests/test_media.py | 59 ++++++++++++++++++++++++++++------ 1 file changed, 50 insertions(+), 9 deletions(-) diff --git a/skrf/media/tests/test_media.py b/skrf/media/tests/test_media.py index e58f73fa1..367bb3363 100644 --- a/skrf/media/tests/test_media.py +++ b/skrf/media/tests/test_media.py @@ -191,15 +191,28 @@ def test_resistor_mismatch(self): name = 'resistor,1ohm' qucs_ntwk = Network(os.path.join(self.files_dir, name + '.s2p')) self.dummy_media.frequency = qucs_ntwk.frequency - - # Test for real and imag port impedance mismatch_z0_tuple = ([25, 50], [25-5j, 50+10j], [25, 50-10j]) + + # Test against Y-parameter defination + def resistor_y_def(media: DefinedGammaZ0, R, **kwargs): + result = media.match(nports=2, **kwargs) + y = np.zeros(shape=result.s.shape, dtype=complex) + R = np.array(R) + y[:, 0, 0] = 1.0 / R + y[:, 1, 1] = 1.0 / R + y[:, 0, 1] = -1.0 / R + y[:, 1, 0] = -1.0 / R + result.y = y + return result + + # Test real and imag port impedance with Y-parameter defination and renormalize() for mismatch_z0 in mismatch_z0_tuple: qucs_ntwk_copy = qucs_ntwk.copy() qucs_ntwk_copy.renormalize(mismatch_z0) skrf_ntwk = self.dummy_media.resistor(1, name = name, z0=mismatch_z0) + skrf_ntwk_y = resistor_y_def(self.dummy_media, 1, z0=mismatch_z0) self.assertEqual(qucs_ntwk_copy, skrf_ntwk) - self.assertEqual(qucs_ntwk_copy.s_def, skrf_ntwk.s_def) + assert_array_almost_equal(skrf_ntwk.s, skrf_ntwk_y.s) def test_shunt_resistor(self): """ @@ -246,15 +259,29 @@ def test_capacitor_mismatch(self): name = 'capacitor,p01pF' qucs_ntwk = Network(os.path.join(self.files_dir, name + '.s2p')) self.dummy_media.frequency = qucs_ntwk.frequency - - # Test for real and imag port impedance mismatch_z0_tuple = ([25, 50], [25-5j, 50+10j], [25, 50-10j]) + + # Test against Y-parameter defination + def capacitor_y_def(media: DefinedGammaZ0, C, **kwargs): + result = media.match(nports=2, **kwargs) + w = media.frequency.w + y = np.zeros(shape=result.s.shape, dtype=complex) + C = np.array(C) + y[:, 0, 0] = 1j * w * C + y[:, 1, 1] = 1j * w * C + y[:, 0, 1] = -1j * w * C + y[:, 1, 0] = -1j * w * C + result.y = y + return result + + # Test real and imag port impedance with Y-parameter defination and renormalize() for mismatch_z0 in mismatch_z0_tuple: qucs_ntwk_copy = qucs_ntwk.copy() qucs_ntwk_copy.renormalize(mismatch_z0) skrf_ntwk = self.dummy_media.capacitor(.01e-12, name = name, z0=mismatch_z0) + skrf_ntwk_y = capacitor_y_def(self.dummy_media, .01e-12, z0=mismatch_z0) self.assertEqual(qucs_ntwk_copy, skrf_ntwk) - self.assertEqual(qucs_ntwk_copy.s_def, skrf_ntwk.s_def) + assert_array_almost_equal(skrf_ntwk.s, skrf_ntwk_y.s) def test_shunt_capacitor(self): """ @@ -332,15 +359,29 @@ def test_inductor_mismatch(self): name = 'inductor,p1nH' qucs_ntwk = Network(os.path.join(self.files_dir, name + '.s2p')) self.dummy_media.frequency = qucs_ntwk.frequency - - # Test for real and imag port impedance mismatch_z0_tuple = ([25, 50], [25-5j, 50+10j], [25, 50-10j]) + + # Test against Y-parameter defination + def inductor_y_def(media: DefinedGammaZ0, L, **kwargs): + result = media.match(nports=2, **kwargs) + w = media.frequency.w + y = np.zeros(shape=result.s.shape, dtype=complex) + L = np.array(L) + y[:, 0, 0] = 1.0 / (1j * w * L) + y[:, 1, 1] = 1.0 / (1j * w * L) + y[:, 0, 1] = -1.0 / (1j * w * L) + y[:, 1, 0] = -1.0 / (1j * w * L) + result.y = y + return result + + # Test real and imag port impedance with Y-parameter defination and renormalize() for mismatch_z0 in mismatch_z0_tuple: qucs_ntwk_copy = qucs_ntwk.copy() qucs_ntwk_copy.renormalize(mismatch_z0) skrf_ntwk = self.dummy_media.inductor(.1e-9, name = name, z0=mismatch_z0) + skrf_ntwk_y = inductor_y_def(self.dummy_media, .1e-9, z0=mismatch_z0) self.assertEqual(qucs_ntwk_copy, skrf_ntwk) - self.assertEqual(qucs_ntwk_copy.s_def, skrf_ntwk.s_def) + assert_array_almost_equal(skrf_ntwk.s, skrf_ntwk_y.s) def test_shunt_inductor(self): """ From 817ecaa884b1c3cf93c539ef958c61a62f925962 Mon Sep 17 00:00:00 2001 From: Asachoo Date: Sat, 16 Nov 2024 11:35:13 +0800 Subject: [PATCH 34/58] Added s_def detection for s-parameters. --- skrf/media/tests/test_media.py | 57 ++++++++++++++++++++++++++++++++++ 1 file changed, 57 insertions(+) diff --git a/skrf/media/tests/test_media.py b/skrf/media/tests/test_media.py index 367bb3363..ac2cd9c5e 100644 --- a/skrf/media/tests/test_media.py +++ b/skrf/media/tests/test_media.py @@ -214,6 +214,25 @@ def resistor_y_def(media: DefinedGammaZ0, R, **kwargs): self.assertEqual(qucs_ntwk_copy, skrf_ntwk) assert_array_almost_equal(skrf_ntwk.s, skrf_ntwk_y.s) + def test_resistor_sdef(self): + """ + Compare the component values against s-parameters generated by QUCS. + The different s_def QUCS component is generated by renormalize() method. + """ + # Config the qucs_ntwk and media + name = 'resistor,1ohm' + qucs_ntwk = Network(os.path.join(self.files_dir, name + '.s2p')) + self.dummy_media.frequency = qucs_ntwk.frequency + z0 = [25, 50-10j] + s_defs = ("power", "pseudo", "traveling") + + # Test different s_def configurations + for s_def in s_defs: + qucs_ntwk_copy = qucs_ntwk.copy() + qucs_ntwk_copy.renormalize(z0, s_def=s_def) + skrf_ntwk = self.dummy_media.resistor(1, name=name, z0=z0, s_def=s_def) + self.assertEqual(qucs_ntwk_copy, skrf_ntwk) + def test_shunt_resistor(self): """ Test the naming of the network. When circuit is used to connect a @@ -283,6 +302,25 @@ def capacitor_y_def(media: DefinedGammaZ0, C, **kwargs): self.assertEqual(qucs_ntwk_copy, skrf_ntwk) assert_array_almost_equal(skrf_ntwk.s, skrf_ntwk_y.s) + def test_capacitor_sdef(self): + """ + Compare the component values against s-parameters generated by QUCS. + The different s_def QUCS component is generated by renormalize() method. + """ + # Config the qucs_ntwk and media + name = 'capacitor,p01pF' + qucs_ntwk = Network(os.path.join(self.files_dir, name + '.s2p')) + self.dummy_media.frequency = qucs_ntwk.frequency + z0 = [25, 50-10j] + s_defs = ("power", "pseudo", "traveling") + + # Test different s_def configurations + for s_def in s_defs: + qucs_ntwk_copy = qucs_ntwk.copy() + qucs_ntwk_copy.renormalize(z0, s_def=s_def) + skrf_ntwk = self.dummy_media.capacitor(.01e-12, name=name, z0=z0, s_def=s_def) + self.assertEqual(qucs_ntwk_copy, skrf_ntwk) + def test_shunt_capacitor(self): """ Test the naming of the network. When circuit is used to connect a @@ -383,6 +421,25 @@ def inductor_y_def(media: DefinedGammaZ0, L, **kwargs): self.assertEqual(qucs_ntwk_copy, skrf_ntwk) assert_array_almost_equal(skrf_ntwk.s, skrf_ntwk_y.s) + def test_inductor_sdef(self): + """ + Compare the component values against s-parameters generated by QUCS. + The different s_def QUCS component is generated by renormalize() method. + """ + # Config the qucs_ntwk and media + name = 'inductor,p1nH' + qucs_ntwk = Network(os.path.join(self.files_dir, name + '.s2p')) + self.dummy_media.frequency = qucs_ntwk.frequency + z0 = [25, 50-10j] + s_defs = ("power", "pseudo", "traveling") + + # Test different s_def configurations + for s_def in s_defs: + qucs_ntwk_copy = qucs_ntwk.copy() + qucs_ntwk_copy.renormalize(z0, s_def=s_def) + skrf_ntwk = self.dummy_media.inductor(.1e-9, name=name, z0=z0, s_def=s_def) + self.assertEqual(qucs_ntwk_copy, skrf_ntwk) + def test_shunt_inductor(self): """ Test the naming of the network. When circuit is used to connect a From 20709180ce674c3b5166d65b3c8c6acdf85249e9 Mon Sep 17 00:00:00 2001 From: Asachoo Date: Sat, 16 Nov 2024 12:13:04 +0800 Subject: [PATCH 35/58] Added Y-parameter definition reference when s_def detection. --- skrf/media/tests/test_media.py | 57 ++++++++++++++++++++++++++++++++-- 1 file changed, 54 insertions(+), 3 deletions(-) diff --git a/skrf/media/tests/test_media.py b/skrf/media/tests/test_media.py index ac2cd9c5e..a71d17a59 100644 --- a/skrf/media/tests/test_media.py +++ b/skrf/media/tests/test_media.py @@ -226,12 +226,28 @@ def test_resistor_sdef(self): z0 = [25, 50-10j] s_defs = ("power", "pseudo", "traveling") + # Test against Y-parameter defination + def resistor_y_def(media: DefinedGammaZ0, R, **kwargs): + result = media.match(nports=2, **kwargs) + y = np.zeros(shape=result.s.shape, dtype=complex) + R = np.array(R) + y[:, 0, 0] = 1.0 / R + y[:, 1, 1] = 1.0 / R + y[:, 0, 1] = -1.0 / R + y[:, 1, 0] = -1.0 / R + result.y = y + return result + # Test different s_def configurations for s_def in s_defs: qucs_ntwk_copy = qucs_ntwk.copy() qucs_ntwk_copy.renormalize(z0, s_def=s_def) skrf_ntwk = self.dummy_media.resistor(1, name=name, z0=z0, s_def=s_def) - self.assertEqual(qucs_ntwk_copy, skrf_ntwk) + skrf_ntwk_y = resistor_y_def(self.dummy_media, 1, z0=z0, s_def=s_def) + self.assertEqual(skrf_ntwk, qucs_ntwk_copy) + self.assertEqual(skrf_ntwk.s_def, qucs_ntwk_copy.s_def) + assert_array_almost_equal(skrf_ntwk.s, skrf_ntwk_y.s) + self.assertEqual(skrf_ntwk.s_def, skrf_ntwk_y.s_def) def test_shunt_resistor(self): """ @@ -314,12 +330,29 @@ def test_capacitor_sdef(self): z0 = [25, 50-10j] s_defs = ("power", "pseudo", "traveling") + # Test against Y-parameter defination + def capacitor_y_def(media: DefinedGammaZ0, C, **kwargs): + result = media.match(nports=2, **kwargs) + w = media.frequency.w + y = np.zeros(shape=result.s.shape, dtype=complex) + C = np.array(C) + y[:, 0, 0] = 1j * w * C + y[:, 1, 1] = 1j * w * C + y[:, 0, 1] = -1j * w * C + y[:, 1, 0] = -1j * w * C + result.y = y + return result + # Test different s_def configurations for s_def in s_defs: qucs_ntwk_copy = qucs_ntwk.copy() qucs_ntwk_copy.renormalize(z0, s_def=s_def) skrf_ntwk = self.dummy_media.capacitor(.01e-12, name=name, z0=z0, s_def=s_def) - self.assertEqual(qucs_ntwk_copy, skrf_ntwk) + skrf_ntwk_y = capacitor_y_def(self.dummy_media, .01e-12, z0=z0, s_def=s_def) + self.assertEqual(skrf_ntwk, qucs_ntwk_copy) + self.assertEqual(skrf_ntwk.s_def, qucs_ntwk_copy.s_def) + assert_array_almost_equal(skrf_ntwk.s, skrf_ntwk_y.s) + self.assertEqual(skrf_ntwk.s_def, skrf_ntwk_y.s_def) def test_shunt_capacitor(self): """ @@ -433,12 +466,30 @@ def test_inductor_sdef(self): z0 = [25, 50-10j] s_defs = ("power", "pseudo", "traveling") + # Test against Y-parameter defination + def inductor_y_def(media: DefinedGammaZ0, L, **kwargs): + result = media.match(nports=2, **kwargs) + w = media.frequency.w + y = np.zeros(shape=result.s.shape, dtype=complex) + L = np.array(L) + y[:, 0, 0] = 1.0 / (1j * w * L) + y[:, 1, 1] = 1.0 / (1j * w * L) + y[:, 0, 1] = -1.0 / (1j * w * L) + y[:, 1, 0] = -1.0 / (1j * w * L) + result.y = y + return result + # Test different s_def configurations for s_def in s_defs: qucs_ntwk_copy = qucs_ntwk.copy() qucs_ntwk_copy.renormalize(z0, s_def=s_def) skrf_ntwk = self.dummy_media.inductor(.1e-9, name=name, z0=z0, s_def=s_def) - self.assertEqual(qucs_ntwk_copy, skrf_ntwk) + skrf_ntwk_y = inductor_y_def(self.dummy_media, .1e-9, z0=z0, s_def=s_def) + self.assertEqual(skrf_ntwk, qucs_ntwk_copy) + self.assertEqual(skrf_ntwk.s_def, qucs_ntwk_copy.s_def) + # numerical precision is slightly reduced when using pseudo s_def + assert_array_almost_equal(skrf_ntwk.s, skrf_ntwk_y.s, decimal={'pseudo': 5}.get(s_def, 6)) + self.assertEqual(skrf_ntwk.s_def, skrf_ntwk_y.s_def) def test_shunt_inductor(self): """ From ce511ffb02942d2a51c5442d4ed8634aa4971137 Mon Sep 17 00:00:00 2001 From: Asachoo Date: Sat, 16 Nov 2024 18:28:52 +0800 Subject: [PATCH 36/58] Modify the frequency unit of the test case. --- skrf/io/tests/test_ts_spec.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/skrf/io/tests/test_ts_spec.py b/skrf/io/tests/test_ts_spec.py index 4d7affb2a..1cd61956f 100644 --- a/skrf/io/tests/test_ts_spec.py +++ b/skrf/io/tests/test_ts_spec.py @@ -59,7 +59,7 @@ def test_ex_4(): [-79.34, -66.58, -42.2 , 161.24]]]) s = s_mag * np.exp(1j*s_deg * np.pi / 180) -ex_5_6 = rf.Network(s=np.tile(s, [2, 1, 1]), z0=[50, 75, 0.01, 0.01], f=[5e9, 6e9], f_unit="Hz") +ex_5_6 = rf.Network(s=np.tile(s, [2, 1, 1]), z0=[50, 75, 0.01, 0.01], f=[5, 6], f_unit="GHz") @pytest.mark.parametrize("fname", [ From 71b812d51d8838712a94c93284fbfe7684706050 Mon Sep 17 00:00:00 2001 From: Asachoo Date: Sun, 17 Nov 2024 09:33:05 +0800 Subject: [PATCH 37/58] Modify the Network name of the failed test case. --- skrf/io/tests/test_ts_spec.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/skrf/io/tests/test_ts_spec.py b/skrf/io/tests/test_ts_spec.py index 1cd61956f..1f4eb01a3 100644 --- a/skrf/io/tests/test_ts_spec.py +++ b/skrf/io/tests/test_ts_spec.py @@ -59,7 +59,7 @@ def test_ex_4(): [-79.34, -66.58, -42.2 , 161.24]]]) s = s_mag * np.exp(1j*s_deg * np.pi / 180) -ex_5_6 = rf.Network(s=np.tile(s, [2, 1, 1]), z0=[50, 75, 0.01, 0.01], f=[5, 6], f_unit="GHz") +ex_5_6 = rf.Network(s=np.tile(s, [2, 1, 1]), z0=[50, 75, 0.01, 0.01], f=[5, 6], f_unit="GHz", name='ex_6') @pytest.mark.parametrize("fname", [ From 89f19d9ed46bae50b152854a3dd250be7b8f6172 Mon Sep 17 00:00:00 2001 From: Asachoo Date: Sun, 17 Nov 2024 11:09:15 +0800 Subject: [PATCH 38/58] Revert the fixes attempted for test_ts_example_5_6(). --- skrf/io/tests/test_ts_spec.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/skrf/io/tests/test_ts_spec.py b/skrf/io/tests/test_ts_spec.py index 1f4eb01a3..4d7affb2a 100644 --- a/skrf/io/tests/test_ts_spec.py +++ b/skrf/io/tests/test_ts_spec.py @@ -59,7 +59,7 @@ def test_ex_4(): [-79.34, -66.58, -42.2 , 161.24]]]) s = s_mag * np.exp(1j*s_deg * np.pi / 180) -ex_5_6 = rf.Network(s=np.tile(s, [2, 1, 1]), z0=[50, 75, 0.01, 0.01], f=[5, 6], f_unit="GHz", name='ex_6') +ex_5_6 = rf.Network(s=np.tile(s, [2, 1, 1]), z0=[50, 75, 0.01, 0.01], f=[5e9, 6e9], f_unit="Hz") @pytest.mark.parametrize("fname", [ From 02f4f686c4a36704493681ac64fd5eba4a048891 Mon Sep 17 00:00:00 2001 From: Asachoo Date: Sun, 17 Nov 2024 13:31:20 +0800 Subject: [PATCH 39/58] Merging unit test cases. --- skrf/media/tests/test_media.py | 92 ++++++---------------------------- 1 file changed, 15 insertions(+), 77 deletions(-) diff --git a/skrf/media/tests/test_media.py b/skrf/media/tests/test_media.py index a71d17a59..d645cea92 100644 --- a/skrf/media/tests/test_media.py +++ b/skrf/media/tests/test_media.py @@ -214,36 +214,16 @@ def resistor_y_def(media: DefinedGammaZ0, R, **kwargs): self.assertEqual(qucs_ntwk_copy, skrf_ntwk) assert_array_almost_equal(skrf_ntwk.s, skrf_ntwk_y.s) - def test_resistor_sdef(self): - """ - Compare the component values against s-parameters generated by QUCS. - The different s_def QUCS component is generated by renormalize() method. - """ - # Config the qucs_ntwk and media - name = 'resistor,1ohm' - qucs_ntwk = Network(os.path.join(self.files_dir, name + '.s2p')) - self.dummy_media.frequency = qucs_ntwk.frequency - z0 = [25, 50-10j] + # Config the s_def parameters + mismatch_z0 = [25, 50-10j] s_defs = ("power", "pseudo", "traveling") - # Test against Y-parameter defination - def resistor_y_def(media: DefinedGammaZ0, R, **kwargs): - result = media.match(nports=2, **kwargs) - y = np.zeros(shape=result.s.shape, dtype=complex) - R = np.array(R) - y[:, 0, 0] = 1.0 / R - y[:, 1, 1] = 1.0 / R - y[:, 0, 1] = -1.0 / R - y[:, 1, 0] = -1.0 / R - result.y = y - return result - # Test different s_def configurations for s_def in s_defs: qucs_ntwk_copy = qucs_ntwk.copy() - qucs_ntwk_copy.renormalize(z0, s_def=s_def) - skrf_ntwk = self.dummy_media.resistor(1, name=name, z0=z0, s_def=s_def) - skrf_ntwk_y = resistor_y_def(self.dummy_media, 1, z0=z0, s_def=s_def) + qucs_ntwk_copy.renormalize(mismatch_z0, s_def=s_def) + skrf_ntwk = self.dummy_media.resistor(1, name=name, z0=mismatch_z0, s_def=s_def) + skrf_ntwk_y = resistor_y_def(self.dummy_media, 1, z0=mismatch_z0, s_def=s_def) self.assertEqual(skrf_ntwk, qucs_ntwk_copy) self.assertEqual(skrf_ntwk.s_def, qucs_ntwk_copy.s_def) assert_array_almost_equal(skrf_ntwk.s, skrf_ntwk_y.s) @@ -318,37 +298,16 @@ def capacitor_y_def(media: DefinedGammaZ0, C, **kwargs): self.assertEqual(qucs_ntwk_copy, skrf_ntwk) assert_array_almost_equal(skrf_ntwk.s, skrf_ntwk_y.s) - def test_capacitor_sdef(self): - """ - Compare the component values against s-parameters generated by QUCS. - The different s_def QUCS component is generated by renormalize() method. - """ - # Config the qucs_ntwk and media - name = 'capacitor,p01pF' - qucs_ntwk = Network(os.path.join(self.files_dir, name + '.s2p')) - self.dummy_media.frequency = qucs_ntwk.frequency - z0 = [25, 50-10j] + # Config the s_def parameters + mismatch_z0 = [25, 50-10j] s_defs = ("power", "pseudo", "traveling") - # Test against Y-parameter defination - def capacitor_y_def(media: DefinedGammaZ0, C, **kwargs): - result = media.match(nports=2, **kwargs) - w = media.frequency.w - y = np.zeros(shape=result.s.shape, dtype=complex) - C = np.array(C) - y[:, 0, 0] = 1j * w * C - y[:, 1, 1] = 1j * w * C - y[:, 0, 1] = -1j * w * C - y[:, 1, 0] = -1j * w * C - result.y = y - return result - # Test different s_def configurations for s_def in s_defs: qucs_ntwk_copy = qucs_ntwk.copy() - qucs_ntwk_copy.renormalize(z0, s_def=s_def) - skrf_ntwk = self.dummy_media.capacitor(.01e-12, name=name, z0=z0, s_def=s_def) - skrf_ntwk_y = capacitor_y_def(self.dummy_media, .01e-12, z0=z0, s_def=s_def) + qucs_ntwk_copy.renormalize(mismatch_z0, s_def=s_def) + skrf_ntwk = self.dummy_media.capacitor(.01e-12, name=name, z0=mismatch_z0, s_def=s_def) + skrf_ntwk_y = capacitor_y_def(self.dummy_media, .01e-12, z0=mismatch_z0, s_def=s_def) self.assertEqual(skrf_ntwk, qucs_ntwk_copy) self.assertEqual(skrf_ntwk.s_def, qucs_ntwk_copy.s_def) assert_array_almost_equal(skrf_ntwk.s, skrf_ntwk_y.s) @@ -454,37 +413,16 @@ def inductor_y_def(media: DefinedGammaZ0, L, **kwargs): self.assertEqual(qucs_ntwk_copy, skrf_ntwk) assert_array_almost_equal(skrf_ntwk.s, skrf_ntwk_y.s) - def test_inductor_sdef(self): - """ - Compare the component values against s-parameters generated by QUCS. - The different s_def QUCS component is generated by renormalize() method. - """ - # Config the qucs_ntwk and media - name = 'inductor,p1nH' - qucs_ntwk = Network(os.path.join(self.files_dir, name + '.s2p')) - self.dummy_media.frequency = qucs_ntwk.frequency - z0 = [25, 50-10j] + # Config the s_def parameters + mismatch_z0 = [25, 50-10j] s_defs = ("power", "pseudo", "traveling") - # Test against Y-parameter defination - def inductor_y_def(media: DefinedGammaZ0, L, **kwargs): - result = media.match(nports=2, **kwargs) - w = media.frequency.w - y = np.zeros(shape=result.s.shape, dtype=complex) - L = np.array(L) - y[:, 0, 0] = 1.0 / (1j * w * L) - y[:, 1, 1] = 1.0 / (1j * w * L) - y[:, 0, 1] = -1.0 / (1j * w * L) - y[:, 1, 0] = -1.0 / (1j * w * L) - result.y = y - return result - # Test different s_def configurations for s_def in s_defs: qucs_ntwk_copy = qucs_ntwk.copy() - qucs_ntwk_copy.renormalize(z0, s_def=s_def) - skrf_ntwk = self.dummy_media.inductor(.1e-9, name=name, z0=z0, s_def=s_def) - skrf_ntwk_y = inductor_y_def(self.dummy_media, .1e-9, z0=z0, s_def=s_def) + qucs_ntwk_copy.renormalize(mismatch_z0, s_def=s_def) + skrf_ntwk = self.dummy_media.inductor(.1e-9, name=name, z0=mismatch_z0, s_def=s_def) + skrf_ntwk_y = inductor_y_def(self.dummy_media, .1e-9, z0=mismatch_z0, s_def=s_def) self.assertEqual(skrf_ntwk, qucs_ntwk_copy) self.assertEqual(skrf_ntwk.s_def, qucs_ntwk_copy.s_def) # numerical precision is slightly reduced when using pseudo s_def From 5ebd8caaf47df30677e53f5111adec82d4c59714 Mon Sep 17 00:00:00 2001 From: Franz Date: Sun, 17 Nov 2024 16:35:41 +0100 Subject: [PATCH 40/58] debug testing in ci runner --- skrf/network.py | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/skrf/network.py b/skrf/network.py index 125add880..65e8bb419 100644 --- a/skrf/network.py +++ b/skrf/network.py @@ -778,19 +778,27 @@ def __div__(self, other: Network) -> Network: def __eq__(self, other: object) -> bool: if not isinstance(other, self.__class__): + print("No Network") return False if len(self.f) != len(other.f): + print("Different length") return False for prop in ['f','s','z0']: + print(prop) + print(getattr(self, prop)) + print(getattr(other, prop)) if not np.all(np.abs(getattr(self,prop)-getattr(other,prop))< ZERO): + print("False", prop) return False # if z0 is imaginary s_def is compared. If real of z0 is equal but s_def differs, networks are still equal if ((np.imag(self.z0) != 0).all()) or ((np.imag(other.z0) != 0).all()): + print(self.s_def, other.s_def) if self.s_def == other.s_def: return True else: return np.allclose(self.z0.real, other.z0.real, atol = ZERO) else: + print("Finally true") return True def __ne__(self, other:object) -> bool: From 5761fd841da1879265e025154821f0889fdcf958 Mon Sep 17 00:00:00 2001 From: Franz Date: Sun, 17 Nov 2024 16:40:18 +0100 Subject: [PATCH 41/58] Enable debbug logging --- .github/workflows/testing.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/testing.yml b/.github/workflows/testing.yml index 0964c51d5..bb21d85e2 100644 --- a/.github/workflows/testing.yml +++ b/.github/workflows/testing.yml @@ -34,7 +34,7 @@ jobs: run: | uv pip install -e .[test,visa,netw,xlsx,plot,docs,testspice] --compile . .venv/bin/activate - python -m pytest -n auto --junitxml=test-results/junit-${{ matrix.python-version }}.xml --junit-prefix=${{ matrix.python-version }} + python -m pytest -n auto --junitxml=test-results/junit-${{ matrix.python-version }}.xml --junit-prefix=${{ matrix.python-version }} -vvv -s - name: Upload Test Results if: always() From 78699aa7492764a212c35bbba35dc6b6f0550524 Mon Sep 17 00:00:00 2001 From: Franz Date: Sun, 17 Nov 2024 16:44:36 +0100 Subject: [PATCH 42/58] Disable parallel runner --- .github/workflows/testing.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/testing.yml b/.github/workflows/testing.yml index bb21d85e2..ba9bcc031 100644 --- a/.github/workflows/testing.yml +++ b/.github/workflows/testing.yml @@ -34,7 +34,7 @@ jobs: run: | uv pip install -e .[test,visa,netw,xlsx,plot,docs,testspice] --compile . .venv/bin/activate - python -m pytest -n auto --junitxml=test-results/junit-${{ matrix.python-version }}.xml --junit-prefix=${{ matrix.python-version }} -vvv -s + python -m pytest --junitxml=test-results/junit-${{ matrix.python-version }}.xml --junit-prefix=${{ matrix.python-version }} -vvv -s - name: Upload Test Results if: always() From 9d2999c3154de021d187292c665287e20bf05061 Mon Sep 17 00:00:00 2001 From: Franz Forstmayr Date: Sun, 17 Nov 2024 16:56:18 +0100 Subject: [PATCH 43/58] Revert --- .github/workflows/testing.yml | 2 +- skrf/network.py | 8 -------- 2 files changed, 1 insertion(+), 9 deletions(-) diff --git a/.github/workflows/testing.yml b/.github/workflows/testing.yml index ba9bcc031..0964c51d5 100644 --- a/.github/workflows/testing.yml +++ b/.github/workflows/testing.yml @@ -34,7 +34,7 @@ jobs: run: | uv pip install -e .[test,visa,netw,xlsx,plot,docs,testspice] --compile . .venv/bin/activate - python -m pytest --junitxml=test-results/junit-${{ matrix.python-version }}.xml --junit-prefix=${{ matrix.python-version }} -vvv -s + python -m pytest -n auto --junitxml=test-results/junit-${{ matrix.python-version }}.xml --junit-prefix=${{ matrix.python-version }} - name: Upload Test Results if: always() diff --git a/skrf/network.py b/skrf/network.py index 65e8bb419..125add880 100644 --- a/skrf/network.py +++ b/skrf/network.py @@ -778,27 +778,19 @@ def __div__(self, other: Network) -> Network: def __eq__(self, other: object) -> bool: if not isinstance(other, self.__class__): - print("No Network") return False if len(self.f) != len(other.f): - print("Different length") return False for prop in ['f','s','z0']: - print(prop) - print(getattr(self, prop)) - print(getattr(other, prop)) if not np.all(np.abs(getattr(self,prop)-getattr(other,prop))< ZERO): - print("False", prop) return False # if z0 is imaginary s_def is compared. If real of z0 is equal but s_def differs, networks are still equal if ((np.imag(self.z0) != 0).all()) or ((np.imag(other.z0) != 0).all()): - print(self.s_def, other.s_def) if self.s_def == other.s_def: return True else: return np.allclose(self.z0.real, other.z0.real, atol = ZERO) else: - print("Finally true") return True def __ne__(self, other:object) -> bool: From ff462aff290eb764ed3aaeb4b1db56d4848771a0 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Henrik=20Forst=C3=A9n?= Date: Sun, 17 Nov 2024 18:18:40 +0200 Subject: [PATCH 44/58] line_floating S-parameter definition (#1207) --- skrf/media/media.py | 33 +++++++++--------------- skrf/media/tests/test_media.py | 47 ++++++++++++++++++++++++++++++++++ 2 files changed, 59 insertions(+), 21 deletions(-) diff --git a/skrf/media/media.py b/skrf/media/media.py index 33ef32f0b..34096d56f 100644 --- a/skrf/media/media.py +++ b/skrf/media/media.py @@ -1055,27 +1055,18 @@ def line_floating(self, d: NumberLike, unit: str = 'deg', # conjugation. result = self.match(nports=4, z0=z0, s_def='traveling', **kwargs) - theta = self.electrical_length(self.to_meters(d=d, unit=unit)) - - if np.abs(theta).all() < ZERO: - result.s = 1/2* np.array([[1, 1, 1, -1], - [1, 1, -1, 1], - [1, -1, 1, 1], - [-1, 1, 1, 1]] - ).transpose().reshape(-1,4,4) - else: - # From AWR docs on TLINP4. The math below could - # be re-worked directly into its S-parameter formulation - y11 = 1 / (z0 * np.tanh(theta)) - y12 = -1 / (z0 * np.sinh(theta)) - y22 = y11 - y21 = y12 - - result.y = \ - np.array([[ y11, y12, -y11, -y12], - [ y21, y22, -y21, -y22], - [-y11, -y12, y11, y12], - [-y21, -y22, y21, y22]]).transpose().reshape(-1, 4, 4) + t = self.electrical_length(self.to_meters(d=d, unit=unit)) + + denom = -1 + 9*np.exp(2*t) + s11 = (1 + 3*np.exp(2*t)) / denom + s12 = 4*np.exp(t) / denom + s13 = (-2 + 6*np.exp(2*t)) / denom + s14 = -s12 + + result.s = np.array([[s11, s12, s13, s14], + [s12, s11, s14, s13], + [s13, s14, s11, s12], + [s14, s13, s12, s11]]).transpose(2, 0, 1) # renormalize (or embed) into z0_port if required if self.z0_port is not None: diff --git a/skrf/media/tests/test_media.py b/skrf/media/tests/test_media.py index 4e738812b..7c06a56b3 100644 --- a/skrf/media/tests/test_media.py +++ b/skrf/media/tests/test_media.py @@ -4,6 +4,7 @@ import numpy as np from numpy.testing import assert_array_almost_equal +from skrf.constants import S_DEF_DEFAULT from skrf.frequency import Frequency from skrf.media import DefinedGammaZ0 from skrf.network import Network, concat_ports, connect @@ -391,6 +392,52 @@ def test_line_floating(self): zero_length = self.dummy_media.line_floating(d=0, z0=z0) assert_array_almost_equal(zero_length.s, s_zero) + def test_line_floating2(self): + """ + Test against line + """ + d = 100 + unit = 'mm' + z0 = 50 + line = self.dummy_media.line(d, unit, z0) + line_floating = self.dummy_media.line_floating(d, unit, z0) + gnd = self.dummy_media.short(n_ports=1) + line_floating = connect(line_floating, 3, gnd, 0) + line_floating = connect(line_floating, 2, gnd, 0) + assert_array_almost_equal(line_floating.s, line.s) + + def test_line_floating3(self): + """ + Test against Y-parameter definition + """ + d = 100 + unit = 'mm' + + def yparam_ref(media, d, unit, z0, s_def=S_DEF_DEFAULT): + result = media.match(nports=4, z0=z0, s_def='traveling') + + theta = media.electrical_length(media.to_meters(d=d, unit=unit)) + + # From AWR docs on TLINP4. + y11 = 1 / (z0 * np.tanh(theta)) + y12 = -1 / (z0 * np.sinh(theta)) + y22 = y11 + y21 = y12 + + result.y = \ + np.array([[ y11, y12, -y11, -y12], + [ y21, y22, -y21, -y22], + [-y11, -y12, y11, y12], + [-y21, -y22, y21, y22]]).transpose(2,0,1) + if media.z0_port is not None: + result.renormalize(media.z0_port) + result.renormalize(result.z0, s_def=s_def) + return result + + for z0 in [50, 1, 100+10j]: + line = self.dummy_media.line_floating(d, unit, z0) + ref_line = yparam_ref(self.dummy_media, d, unit, z0) + assert_array_almost_equal(ref_line.s, line.s) def test_scalar_gamma_z0_media(self): """ From 4eeca5a0eadffd963d6918dea90b2a33a4aca6f3 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Henrik=20Forst=C3=A9n?= Date: Sun, 17 Nov 2024 19:45:44 +0200 Subject: [PATCH 45/58] Fix uninitialized memory use in touchstone parser --- skrf/io/touchstone.py | 10 +++++++--- 1 file changed, 7 insertions(+), 3 deletions(-) diff --git a/skrf/io/touchstone.py b/skrf/io/touchstone.py index 3c8fc9ee7..d8dfeef9a 100644 --- a/skrf/io/touchstone.py +++ b/skrf/io/touchstone.py @@ -537,7 +537,7 @@ def load_file(self, fid: typing.TextIO): if state.matrix_format == "full": self.s[:] = s_flat else: - index = np.tril_indices(state.rank) if state.matrix_format == "lower" else np.triu_indices(self.rank) + index = np.tril_indices(state.rank) if state.matrix_format == "lower" else np.triu_indices(state.rank) index_flat = np.ravel_multi_index(index, (state.rank, state.rank)) self.s[:, index_flat] = s_flat @@ -546,8 +546,12 @@ def load_file(self, fid: typing.TextIO): else: self.s = self.s.reshape((-1, state.rank, state.rank)) - if state.matrix_format != "full": - self.s = np.nanmax((self.s, self.s.transpose(0, 2, 1)), axis=0) + if state.matrix_format == "upper": + index_lower = np.tril_indices(state.rank) + self.s[(...,*index_lower)] = self.s.transpose(0, 2, 1)[(...,*index_lower)] + elif state.matrix_format == "lower": + index_upper = np.triu_indices(state.rank) + self.s[(...,*index_upper)] = self.s.transpose(0, 2, 1)[(...,*index_upper)] self.port_modes = np.array(["S"] * state.rank) if state.mixed_mode_order: From 334118d6bacefd012f020323852453849c5b3f7f Mon Sep 17 00:00:00 2001 From: Lansus Date: Mon, 18 Nov 2024 09:25:57 +0800 Subject: [PATCH 46/58] Explicitly define s_def as 'power'. --- skrf/media/media.py | 18 +++++++++--------- 1 file changed, 9 insertions(+), 9 deletions(-) diff --git a/skrf/media/media.py b/skrf/media/media.py index ba17d7f3a..f292801fb 100644 --- a/skrf/media/media.py +++ b/skrf/media/media.py @@ -643,8 +643,8 @@ def resistor(self, R: NumberLike, *args, **kwargs) -> Network: capacitor inductor """ - s_def = kwargs.pop('s_def', S_DEF_DEFAULT) - result = self.match(nports=2, s_def=S_DEF_DEFAULT, **kwargs) + s_def = kwargs.pop('s_def', 'power') + result = self.match(nports=2, s_def='power', **kwargs) s = np.zeros(shape=result.s.shape, dtype=complex) R = np.array(R) # Convert Y-parameters resistor to S-parameters in power wave to accommodate any R value. @@ -661,7 +661,7 @@ def resistor(self, R: NumberLike, *args, **kwargs) -> Network: result.s = s # Renormalize into s_def if required - if s_def != S_DEF_DEFAULT: + if s_def != 'power': result.renormalize(z_new=result.z0, s_def=s_def) return result @@ -692,8 +692,8 @@ def capacitor(self, C: NumberLike, **kwargs) -> Network: resistor inductor """ - s_def = kwargs.pop('s_def', S_DEF_DEFAULT) - result = self.match(nports=2, s_def=S_DEF_DEFAULT, **kwargs) + s_def = kwargs.pop('s_def', 'power') + result = self.match(nports=2, s_def='power', **kwargs) w = self.frequency.w s = np.zeros(shape=result.s.shape, dtype=complex) C = np.array(C) @@ -711,7 +711,7 @@ def capacitor(self, C: NumberLike, **kwargs) -> Network: result.s = s # Renormalize into s_def if required - if s_def != S_DEF_DEFAULT: + if s_def != 'power': result.renormalize(z_new=result.z0, s_def=s_def) return result @@ -742,8 +742,8 @@ def inductor(self, L: NumberLike, **kwargs) -> Network: capacitor resistor """ - s_def = kwargs.pop('s_def', S_DEF_DEFAULT) - result = self.match(nports=2, s_def=S_DEF_DEFAULT, **kwargs) + s_def = kwargs.pop('s_def', 'power') + result = self.match(nports=2, s_def='power', **kwargs) w = self.frequency.w s = np.zeros(shape=result.s.shape, dtype=complex) L = np.array(L) @@ -761,7 +761,7 @@ def inductor(self, L: NumberLike, **kwargs) -> Network: result.s = s # Renormalize into s_def if required - if s_def != S_DEF_DEFAULT: + if s_def != 'power': result.renormalize(z_new=result.z0, s_def=s_def) return result From 4d96cfed9e03a2a8544ea2e01520e3d6dd2786fd Mon Sep 17 00:00:00 2001 From: Thomas Schweiger Date: Mon, 18 Nov 2024 03:10:17 +0100 Subject: [PATCH 47/58] Replaced write_spice_subckt_s with the new synthesizer code. This version allows to have a different reference impedance Z0 at each port as suggested by Vincent in the pull request discussion. To make the code even more general, I added the possibility to not set all the reference ports to global ground automatically but instead make them subcircuit pins, so the user can connect their own reference if desired. This can be enabled via a new argument. The default behavior, if not enabled, is the same as with the old write_spice_subcircuit_s, maintaining backwards compatibility. Additionally I added comments that explain the gains of the controlled sources --- skrf/vectorFitting.py | 258 +++++++++--------------------------------- 1 file changed, 51 insertions(+), 207 deletions(-) diff --git a/skrf/vectorFitting.py b/skrf/vectorFitting.py index 20975eb4f..214792d84 100644 --- a/skrf/vectorFitting.py +++ b/skrf/vectorFitting.py @@ -2260,7 +2260,8 @@ def plot_passivation(self, ax: Axes = None) -> Axes: ax.set_ylabel('Max. singular value') return ax - def write_spice_subcircuit_s_new(self, file: str, fitted_model_name: str = "s_equivalent") -> None: + def write_spice_subcircuit_s(self, file: str, fitted_model_name: str = "s_equivalent", + create_reference_pins: bool=False) -> None: """ Creates an equivalent N-port subcircuit based on its vector fitted S parameter responses in spice simulator netlist syntax @@ -2269,23 +2270,24 @@ def write_spice_subcircuit_s_new(self, file: str, fitted_model_name: str = "s_eq ---------- file : str Path and filename including file extension (usually .sNp) for the subcircuit file. + fitted_model_name: str Name of the resulting model, default "s_equivalent" - Returns - ------- - None + create_reference_pins: bool + If set to True, the synthesized subcircuit will have N pin-pairs: + P0, P0_reference, ..., PN, PN_reference - Notes - ------- - This code passed some manual plausibility tests but more - testing might be useful to be confident that the circuits - synthesized with it behave exactly the same as the circuits - synthesized with the current write_spice_subcircuit_s method. + If set to False, the synthesized subcircuit will have N pins + P0, ..., PN + In this case, the reference nodes will be internally connected + to the global ground net 0. - More details about this code are available under this link: + The default is False - https://github.com/scikit-rf/scikit-rf/discussions/1172 + Returns + ------- + None Examples -------- @@ -2294,7 +2296,7 @@ def write_spice_subcircuit_s_new(self, file: str, fitted_model_name: str = "s_eq >>> nw_3port = skrf.Network('my3port.s3p') >>> vf = skrf.VectorFitting(nw_3port) >>> vf.vector_fit(n_poles_real=1, n_poles_cmplx=4) - >>> vf.write_spectre_subcircuit_new_s('/my3port_model.sp') + >>> vf.write_spice_subcircuit_s('/my3port_model.sp') References ---------- @@ -2325,35 +2327,58 @@ def get_new_subckt_identifier(): f.write('* EQUIVALENT CIRCUIT FOR VECTOR FITTED S-MATRIX\n') f.write('* Created using scikit-rf vectorFitting.py\n\n') - # Define the complete equivalent circuit as a subcircuit with one input node per port - # those port nodes are labeled p1, p2, p3, ... - # all ports share a common node for ground reference (node 0) - str_input_nodes = " ".join(map(lambda x: f'p{x + 1}', range(self.network.nports))) + # Create subcircuit pin string and reference nodes + if create_reference_pins == True: + str_input_nodes = " ".join(map(lambda x: f'p{x + 1} r{x + 1}', range(self.network.nports))) + ref_nodes = list(map(lambda x: f'r{x + 1}', range(self.network.nports))) + else: + str_input_nodes = " ".join(map(lambda x: f'p{x + 1}', range(self.network.nports))) + ref_nodes = list(map(lambda x: '0', range(self.network.nports))) f.write(f'.SUBCKT {fitted_model_name} {str_input_nodes}\n') + for n in range(self.network.nports): f.write(f'\n* Port network for port {n + 1}\n') + # Calculate sqrt of Z0 for port current port + sqrt_Z0_n=np.sqrt(np.real(self.network.z0[0, n])) + # Port reference impedance Z0 f.write(f'R{n + 1} p{n+1} a{n + 1} {np.real(self.network.z0[0, n])}\n') # CCVS implementing the reflected wave b. # Also used as current sensor to measure the input current - f.write(f'H_b_{n + 1} a{n + 1} 0 V_c_{n + 1} 1\n') + # + # The type of the source (voltage source) and its gain 2*sqrt(Z0N) arise from the + # definition of the reflected wave b at port N: bN=(VN-Z0N*IN)/(2*sqrt(Z0N)) + # This equation represents the Kirchhoff voltage law of the port network: + # 2*sqrt(Z0N)*bN=VN-Z0N*IN + # The left hand side of the equation is realized with a (controlled) voltage + # source with a gain of 2*sqrt(Z0N). + f.write(f'H_b_{n + 1} a{n + 1} {ref_nodes[n]} V_c_{n + 1} {2.0*sqrt_Z0_n}\n') f.write(f'* Differential incident wave a sources for transfer from port {n + 1}\n') - # CCVS and VCVS driving the transfer admittances with 2*a*sqrt(Z0) = V + I*Z0 - f.write(f'H_p_{n + 1} nt_p_{n + 1} nts_p_{n + 1} H_b_{n + 1} {np.real(self.network.z0[0, n])}\n') - f.write(f'E_p_{n + 1} nts_p_{n + 1} 0 p{n + 1} 0 1\n') - - # VCVS driving the transfer admittances with -2*a*sqrt(Z0) = -V - I*Z0 - f.write(f'E_n_{n + 1} 0 nt_n_{n + 1} nt_p_{n + 1} 0 1\n') + # CCVS and VCVS driving the transfer admittances with incident wave a = V/(2.0*sqrt(Z0)) + I*sqrt(Z0)/2 + # + # These voltage sources in series realize the incident wave a. The types of the sources + # and their gains arise from the definition of the incident wave a at port N: + # aN=VN/(2*sqrt(Z0N)) + IN*sqrt(Z0N)/2 + # So we need a VCVS with a gain 1/(2*sqrt(Z0N)) in series with a CCVS with a gain sqrt(Z0N)/2 + f.write(f'H_p_{n + 1} nt_p_{n + 1} nts_p_{n + 1} H_b_{n + 1} {0.5*sqrt_Z0_n}\n') + f.write(f'E_p_{n + 1} nts_p_{n + 1} {ref_nodes[n]} p{n + 1} {ref_nodes[n]} {1.0/(2.0*sqrt_Z0_n)}\n') + + # VCVS driving the transfer admittances with -a + # + # This source just copies the a wave and multiplies it by -1 to implement the negative side + # of the differential a wave. The inversion of the sign is done by the connecting the source + # in opposite direction to the reference node. Thus, the gain is 1. + f.write(f'E_n_{n + 1} {ref_nodes[n]} nt_n_{n + 1} nt_p_{n + 1} {ref_nodes[n]} 1\n') f.write(f'* Current sensor on center node for transfer to port {n + 1}\n') # Current sensor for the transfer to current port - f.write(f'V_c_{n + 1} nt_c_{n + 1} 0 0\n') + f.write(f'V_c_{n + 1} nt_c_{n + 1} {ref_nodes[n]} 0\n') for j in range(self.network.nports): f.write(f'* Transfer network from port {j + 1} to port {n + 1}\n') @@ -2408,7 +2433,7 @@ def get_new_subckt_identifier(): gm = b * l * c f.write(node + f' rcl_vccs_admittance res={r} cap={c} ind={l} gm={gm}\n') - f.write('.ENDS {fitted_model_name}\n\n') + f.write(f'.ENDS {fitted_model_name}\n\n') # Subcircuit for an RLCG equivalent admittance of a complex-conjugate pole-residue pair f.write('.SUBCKT rcl_vccs_admittance n_pos n_neg res=1e3 cap=1e-9 ind=100e-12 gm=1e-3\n') @@ -2423,184 +2448,3 @@ def get_new_subckt_identifier(): f.write('L1 n_pos 1 {ind}\n') f.write('R1 1 n_neg {res}\n') f.write('.ENDS rl_admittance\n\n') - - def write_spice_subcircuit_s(self, file: str, fitted_model_name: str = "s_equivalent") -> None: - """ - Creates an equivalent N-port SPICE subcircuit based on its vector fitted S parameter responses. - - Parameters - ---------- - file : str - Path and filename including file extension (usually .sp) for the SPICE subcircuit file. - fitted_model_name: str - Name of the resulting model, default "s_equivalent" - - Returns - ------- - None - - Notes - ----- - In the SPICE subcircuit, all ports will share a common reference node (global SPICE ground on node 0). The - equivalent circuit uses linear dependent current sources on all ports, which are controlled by the currents - through equivalent admittances modelling the parameters from a vector fit. This approach is based on [#]_. - - Examples - -------- - Load and fit the `Network`, then export the equivalent SPICE subcircuit: - - >>> nw_3port = skrf.Network('my3port.s3p') - >>> vf = skrf.VectorFitting(nw_3port) - >>> vf.vector_fit(n_poles_real=1, n_poles_cmplx=4) - >>> vf.write_spice_subcircuit_s('/my3port_model.sp') - - References - ---------- - .. [#] G. Antonini, "SPICE Equivalent Circuits of Frequency-Domain Responses", IEEE Transactions on - Electromagnetic Compatibility, vol. 45, no. 3, pp. 502-512, August 2003, - DOI: https://doi.org/10.1109/TEMC.2003.815528 - """ - - # list of subcircuits for the equivalent admittances - subcircuits = [] - - # provides a unique SPICE subcircuit identifier (X1, X2, X3, ...) - def get_new_subckt_identifier(): - subcircuits.append(f'X{len(subcircuits) + 1}') - return subcircuits[-1] - - # use engineering notation for the numbers in the SPICE file (1000 --> 1k) - formatter = EngFormatter(sep="", places=3, usetex=False) - # replace "micron" sign by "u" and "mega" sign by "meg" - letters_dict = formatter.ENG_PREFIXES - letters_dict.update({-6: 'u', 6: 'meg'}) - formatter.ENG_PREFIXES = letters_dict - - with open(file, 'w') as f: - # write title line - f.write('* EQUIVALENT CIRCUIT FOR VECTOR FITTED S-MATRIX\n') - f.write('* Created using scikit-rf vectorFitting.py\n') - f.write('*\n') - - # define the complete equivalent circuit as a subcircuit with one input node per port - # those port nodes are labeled p1, p2, p3, ... - # all ports share a common node for ground reference (node 0) - str_input_nodes = '' - for n in range(self.network.nports): - str_input_nodes += f'p{n + 1} ' - - f.write(f'.SUBCKT {fitted_model_name} {str_input_nodes}\n') - - for n in range(self.network.nports): - f.write('*\n') - f.write(f'* port {n + 1}\n') - # add port reference impedance z0 (has to be resistive, no imaginary part) - f.write(f'R{n + 1} a{n + 1} 0 {np.real(self.network.z0[0, n])}\n') - - # add dummy voltage sources (V=0) to measure the input current - f.write(f'V{n + 1} p{n + 1} a{n + 1} 0\n') - - # CCVS and VCVS driving the transfer admittances with a = V/2/sqrt(Z0) + I/2*sqrt(Z0) - # In - f.write(f'H{n + 1} nt{n + 1} nts{n + 1} V{n + 1} {np.real(self.network.z0[0, n])}\n') - # Vn - f.write(f'E{n + 1} nts{n + 1} 0 p{n + 1} 0 {1}\n') - - for j in range(self.network.nports): - f.write(f'* transfer network for s{n + 1}{j + 1}\n') - - # stacking order in VectorFitting class variables: - # s11, s12, s13, ..., s21, s22, s23, ... - i_response = n * self.network.nports + j - - # add CCCS to generate the scattered current I_nj at port n - # control current is measured by the dummy voltage source at the transfer network Y_nj - # the scattered current is injected into the port (source positive connected to ground) - f.write(f'F{n + 1}{j + 1} 0 a{n + 1} V{n + 1}{j + 1} ' - f'{formatter(1 / np.real(self.network.z0[0, n]))}\n') - f.write(f'F{n + 1}{j + 1}_inv a{n + 1} 0 V{n + 1}{j + 1}_inv ' - f'{formatter(1 / np.real(self.network.z0[0, n]))}\n') - - # add dummy voltage source (V=0) in series with Y_nj to measure current through transfer admittance - f.write(f'V{n + 1}{j + 1} nt{j + 1} nt{n + 1}{j + 1} 0\n') - f.write(f'V{n + 1}{j + 1}_inv nt{j + 1} nt{n + 1}{j + 1}_inv 0\n') - - # add corresponding transfer admittance Y_nj, which is modulating the control current - # the transfer admittance is a parallel circuit (sum) of individual admittances - f.write(f'* transfer admittances for S{n + 1}{j + 1}\n') - - # start with proportional and constant term of the model - # H(s) = d + s * e model - # Y(s) = G + s * C equivalent admittance - g = self.constant_coeff[i_response] - c = self.proportional_coeff[i_response] - - # add R for constant term - if g < 0: - f.write(f'R{n + 1}{j + 1} nt{n + 1}{j + 1}_inv 0 {formatter(np.abs(1 / g))}\n') - elif g > 0: - f.write(f'R{n + 1}{j + 1} nt{n + 1}{j + 1} 0 {formatter(1 / g)}\n') - - # add C for proportional term - if c < 0: - f.write(f'C{n + 1}{j + 1} nt{n + 1}{j + 1}_inv 0 {formatter(np.abs(c))}\n') - elif c > 0: - f.write(f'C{n + 1}{j + 1} nt{n + 1}{j + 1} 0 {formatter(c)}\n') - - # add pairs of poles and residues - for i_pole in range(len(self.poles)): - pole = self.poles[i_pole] - residue = self.residues[i_response, i_pole] - node = get_new_subckt_identifier() + f' nt{n + 1}{j + 1}' - - if np.real(residue) < 0.0: - # multiplication with -1 required, otherwise the values for RLC would be negative - # this gets compensated by inverting the transfer current direction for this subcircuit - residue = -1 * residue - node += '_inv' - - if np.imag(pole) == 0.0: - # real pole; add rl_admittance - l = 1 / np.real(residue) - r = -1 * np.real(pole) / np.real(residue) - f.write(node + f' 0 rl_admittance res={formatter(r)} ind={formatter(l)}\n') - else: - # complex pole of a conjugate pair; add rcl_vccs_admittance - l = 1 / (2 * np.real(residue)) - b = -2 * (np.real(residue) * np.real(pole) + np.imag(residue) * np.imag(pole)) - r = -1 * np.real(pole) / np.real(residue) - c = 2 * np.real(residue) / (np.abs(pole) ** 2) - gm_add = b * l * c - if gm_add < 0: - m = -1 - else: - m = 1 - f.write(node + f' 0 rcl_vccs_admittance res={formatter(r)} cap={formatter(c)} ' - f'ind={formatter(l)} gm={formatter(np.abs(gm_add))} mult={int(m)}\n') - - f.write('.ENDS s_equivalent\n') - - f.write('*\n') - - # subcircuit for an active RCL+VCCS equivalent admittance Y(s) of a complex-conjugate pole-residue pair H(s) - # Residue: c = c' + j * c" - # Pole: p = p' + j * p" - # H(s) = c / (s - p) + conj(c) / (s - conj(p)) - # = (2 * c' * s - 2 * (c'p' + c"p")) / (s ** 2 - 2 * p' * s + |p| ** 2) - # Y(S) = (1 / L * s + b) / (s ** 2 + R / L * s + 1 / (L * C)) - f.write('.SUBCKT rcl_vccs_admittance n_pos n_neg res=1k cap=1n ind=100p gm=1m mult=1\n') - f.write('L1 n_pos 1 {ind}\n') - f.write('C1 1 2 {cap}\n') - f.write('R1 2 n_neg {res}\n') - f.write('G1 n_pos n_neg 1 2 {gm * mult}\n') - f.write('.ENDS rcl_vccs_admittance\n') - - f.write('*\n') - - # subcircuit for a passive RL equivalent admittance Y(s) of a real pole-residue pair H(s) - # H(s) = c / (s - p) - # Y(s) = 1 / L / (s + s * R / L) - f.write('.SUBCKT rl_admittance n_pos n_neg res=1k ind=100p\n') - f.write('L1 n_pos 1 {ind}\n') - f.write('R1 1 n_neg {res}\n') - f.write('.ENDS rl_admittance\n') From f04c0ebc7427eeed4c51d488e3319389d2208a04 Mon Sep 17 00:00:00 2001 From: Lansus Date: Mon, 18 Nov 2024 12:39:02 +0800 Subject: [PATCH 48/58] kwargs.pop return S_DEF_DEFAULT. --- skrf/media/media.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/skrf/media/media.py b/skrf/media/media.py index f292801fb..222e7cd1a 100644 --- a/skrf/media/media.py +++ b/skrf/media/media.py @@ -643,7 +643,7 @@ def resistor(self, R: NumberLike, *args, **kwargs) -> Network: capacitor inductor """ - s_def = kwargs.pop('s_def', 'power') + s_def = kwargs.pop('s_def', S_DEF_DEFAULT) result = self.match(nports=2, s_def='power', **kwargs) s = np.zeros(shape=result.s.shape, dtype=complex) R = np.array(R) @@ -692,7 +692,7 @@ def capacitor(self, C: NumberLike, **kwargs) -> Network: resistor inductor """ - s_def = kwargs.pop('s_def', 'power') + s_def = kwargs.pop('s_def', S_DEF_DEFAULT) result = self.match(nports=2, s_def='power', **kwargs) w = self.frequency.w s = np.zeros(shape=result.s.shape, dtype=complex) @@ -742,7 +742,7 @@ def inductor(self, L: NumberLike, **kwargs) -> Network: capacitor resistor """ - s_def = kwargs.pop('s_def', 'power') + s_def = kwargs.pop('s_def', S_DEF_DEFAULT) result = self.match(nports=2, s_def='power', **kwargs) w = self.frequency.w s = np.zeros(shape=result.s.shape, dtype=complex) From e3ab9c6a319b9de947765365e8c854663719d239 Mon Sep 17 00:00:00 2001 From: Thomas Schweiger Date: Mon, 18 Nov 2024 19:12:58 +0100 Subject: [PATCH 49/58] Fixed Ruff complaints --- skrf/vectorFitting.py | 7 +------ 1 file changed, 1 insertion(+), 6 deletions(-) diff --git a/skrf/vectorFitting.py b/skrf/vectorFitting.py index 214792d84..5b95789e7 100644 --- a/skrf/vectorFitting.py +++ b/skrf/vectorFitting.py @@ -8,11 +8,6 @@ import numpy as np -try: - from matplotlib.ticker import EngFormatter -except ImportError: - pass - from .util import Axes, axes_kwarg # imports for type hinting @@ -2328,7 +2323,7 @@ def get_new_subckt_identifier(): f.write('* Created using scikit-rf vectorFitting.py\n\n') # Create subcircuit pin string and reference nodes - if create_reference_pins == True: + if create_reference_pins: str_input_nodes = " ".join(map(lambda x: f'p{x + 1} r{x + 1}', range(self.network.nports))) ref_nodes = list(map(lambda x: f'r{x + 1}', range(self.network.nports))) else: From 01c51c7ca72a45e9852f54b03be13861af5eabe9 Mon Sep 17 00:00:00 2001 From: Thomas Schweiger Date: Mon, 18 Nov 2024 19:26:18 +0100 Subject: [PATCH 50/58] Fixed tests. Fixed error message if PySpice is missing. Fixed number of elements in netlist to match new synthesizer --- skrf/tests/test_vectorfitting.py | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/skrf/tests/test_vectorfitting.py b/skrf/tests/test_vectorfitting.py index 785654b13..ff7935241 100644 --- a/skrf/tests/test_vectorfitting.py +++ b/skrf/tests/test_vectorfitting.py @@ -79,7 +79,7 @@ def test_dc(self): @pytest.mark.skipif( "PySpice" not in sys.modules, - reason="Spice subcircuit uses Engformatter which is not available without matplotlib.") + reason="test_spice_subcircuit uses PySpice parser but it is not available.") def test_spice_subcircuit(self): # fit ring slot example network nw = skrf.data.ring_slot @@ -92,15 +92,15 @@ def test_spice_subcircuit(self): tmp_file.close() vf.write_spice_subcircuit_s(name) - # written tmp file should contain 69 lines - with open(name) as f: - parser = SpiceParser(name) + parser = SpiceParser(name) - assert len(parser.subcircuits[0]._statements) == 52 + # Number of elements on global level + assert len(parser.subcircuits[0]._statements) == 38 + # Number of elements in RLCG subckt assert len(parser.subcircuits[1]._statements) == 4 + # Number of elements in RL subckt assert len(parser.subcircuits[2]._statements) == 2 - os.remove(name) def test_read_write_npz(self): From f4180e85a467dc6e374ef684fe00f00509d6bb37 Mon Sep 17 00:00:00 2001 From: Thomas Schweiger Date: Mon, 18 Nov 2024 21:25:14 +0100 Subject: [PATCH 51/58] Bugfix to avoid instance name collisions. Bug occurs for networks with 11 or more ports. --- skrf/vectorFitting.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/skrf/vectorFitting.py b/skrf/vectorFitting.py index 5b95789e7..8fe5405bb 100644 --- a/skrf/vectorFitting.py +++ b/skrf/vectorFitting.py @@ -2339,7 +2339,7 @@ def get_new_subckt_identifier(): sqrt_Z0_n=np.sqrt(np.real(self.network.z0[0, n])) # Port reference impedance Z0 - f.write(f'R{n + 1} p{n+1} a{n + 1} {np.real(self.network.z0[0, n])}\n') + f.write(f'R_ref_{n + 1} p{n+1} a{n + 1} {np.real(self.network.z0[0, n])}\n') # CCVS implementing the reflected wave b. # Also used as current sensor to measure the input current From 59d96d962681599aa153e951dfb055e274f17ac9 Mon Sep 17 00:00:00 2001 From: Thomas Schweiger Date: Mon, 18 Nov 2024 21:33:42 +0100 Subject: [PATCH 52/58] Fixed another bug of similar type that only occurs for number of ports larger or equal to 11 --- skrf/vectorFitting.py | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/skrf/vectorFitting.py b/skrf/vectorFitting.py index 8fe5405bb..8786bdd18 100644 --- a/skrf/vectorFitting.py +++ b/skrf/vectorFitting.py @@ -2390,15 +2390,15 @@ def get_new_subckt_identifier(): # R for constant term if g < 0: - f.write(f'R{n + 1}{j + 1} nt_n_{j + 1} nt_c_{n + 1} {np.abs(1 / g)}\n') + f.write(f'R{n + 1}_{j + 1} nt_n_{j + 1} nt_c_{n + 1} {np.abs(1 / g)}\n') elif g > 0: - f.write(f'R{n + 1}{j + 1} nt_p_{j + 1} nt_c_{n + 1} {1 / g}\n') + f.write(f'R{n + 1}_{j + 1} nt_p_{j + 1} nt_c_{n + 1} {1 / g}\n') # C for proportional term if c < 0: - f.write(f'C{n + 1}{j + 1} nt_n_{j + 1} nt_c_{n + 1} {np.abs(c)}\n') + f.write(f'C{n + 1}_{j + 1} nt_n_{j + 1} nt_c_{n + 1} {np.abs(c)}\n') elif c > 0: - f.write(f'C{n + 1}{j + 1} nt_p_{j + 1} nt_c_{n + 1} {c}\n') + f.write(f'C{n + 1}_{j + 1} nt_p_{j + 1} nt_c_{n + 1} {c}\n') # Transfer admittances represented by poles and residues for i_pole in range(len(self.poles)): From bcd957f26d8dc7b936bc06eea1a6527c634e8336 Mon Sep 17 00:00:00 2001 From: Franz Forstmayr Date: Tue, 19 Nov 2024 21:54:43 +0100 Subject: [PATCH 53/58] Allow pass network name as kwarg for streams --- skrf/network.py | 2 ++ skrf/tests/test_network.py | 8 ++++++++ 2 files changed, 10 insertions(+) diff --git a/skrf/network.py b/skrf/network.py index 125add880..37e0fcc99 100644 --- a/skrf/network.py +++ b/skrf/network.py @@ -429,6 +429,8 @@ def __init__(self, file: str = None, name: str = None, params: dict = None, # allows user to pass StringIO, filename or file obj if isinstance(file, io.StringIO): + if not hasattr(file, "name") and name is not None: + file.name = name self.read_touchstone(file, self.encoding) else: diff --git a/skrf/tests/test_network.py b/skrf/tests/test_network.py index fd117fcb2..5aa589f8d 100644 --- a/skrf/tests/test_network.py +++ b/skrf/tests/test_network.py @@ -329,6 +329,7 @@ def test_constructor_from_stringio(self): sio.name = os.path.basename(filename) # hack a bug to touchstone reader rf.Network(sio) + def test_constructor_from_stringio_hfss(self): filename = os.path.join(self.test_dir, 'hfss_oneport.s1p') with open(filename) as fid: data = fid.read() @@ -336,6 +337,13 @@ def test_constructor_from_stringio(self): sio.name = os.path.basename(filename) # hack a bug to touchstone reader rf.Network(sio) + def test_constructor_from_stringio_name_kwawrg(self): + filename = os.path.join(self.test_dir, 'ntwk1.s2p') + with open(filename) as fid: + data = fid.read() + sio = io.StringIO(data) + rf.Network(sio, name=filename) + def test_different_ext(self): filename= os.path.join(self.test_dir, 'ntwk1.s2p') for par in ["g", "h", "s", "y", "z"]: From d8fb822f8f3c2a5fc5b2f648d3e6b13bd6205bd0 Mon Sep 17 00:00:00 2001 From: Lansus Date: Wed, 20 Nov 2024 10:03:22 +0800 Subject: [PATCH 54/58] Silently modify the frequency with correct scale. --- skrf/io/csv.py | 47 ++++++++++++++++++++++------------------------- 1 file changed, 22 insertions(+), 25 deletions(-) diff --git a/skrf/io/csv.py b/skrf/io/csv.py index 9d5970b70..4b892331d 100644 --- a/skrf/io/csv.py +++ b/skrf/io/csv.py @@ -63,7 +63,7 @@ # delayed imports # from pandas import Series, Index, DataFrame -def read_pna_csv(filename, *args, **kwargs): +def read_pna_csv(filename, *args, **kwargs) -> tuple[str, str, np.ndarray]: r""" Reads data from a csv file written by an Agilient PNA. @@ -86,8 +86,6 @@ def read_pna_csv(filename, *args, **kwargs): data : :class:`numpy.ndarray` An array containing the data. The meaning of which depends on the header. - f_unit : FrequencyUnitT - The frequency unit of the data, could be 'Hz', 'kHz', 'MHz', 'GHz' and 'THz' See Also -------- @@ -95,7 +93,7 @@ def read_pna_csv(filename, *args, **kwargs): Examples -------- - >>> header, comments, data, f_unit = rf.read_pna_csv('myfile.csv') + >>> header, comments, data = rf.read_pna_csv('myfile.csv') """ warn("deprecated", DeprecationWarning, stacklevel=2) with open(filename) as fid: @@ -141,17 +139,16 @@ def read_pna_csv(filename, *args, **kwargs): units_dict: dict[str, FrequencyUnitT] = {k.lower(): k for k in FREQ_UNITS.keys()} - # default unit - unit: FrequencyUnitT = "Hz" + # Get the frequency unit from the header and convert to Hz + unit_raw = header.split(',')[0].strip('Freq')[1:-1] try: - # Parse frequency unit from header, format is like: "Freq(Hz)" - unit_tmp = header.split(',')[0].strip('Freq')[1:-1].lower() + unit_tmp = unit_raw.lower() if unit_tmp in units_dict: - unit = units_dict[unit_tmp] - except Exception: - unit: FrequencyUnitT = "Hz" + data[:, 0] *= units_dict[unit_tmp] + except Exception as exc: + raise ValueError(f"Could not parse frequency unit '{unit_raw}'") from exc - return header, comments, data, unit + return header, comments, data def pna_csv_2_df(filename): """ @@ -169,7 +166,7 @@ def pna_csv_2_df(filename): """ warn("deprecated", DeprecationWarning, stacklevel=2) from pandas import DataFrame, Index - header, comments, d, f_unit = read_pna_csv(filename) + header, comments, d = read_pna_csv(filename) names = header.split(',') @@ -180,7 +177,7 @@ def pna_csv_2_df(filename): def pna_csv_2_ntwks2(filename, *args, **kwargs): warn("deprecated", DeprecationWarning, stacklevel=2) df = pna_csv_2_df(filename, *args, **kwargs) - header, comments, d, f_unit = read_pna_csv(filename) + header, comments, d = read_pna_csv(filename) ntwk_dict = {} param_set=set([k[:3] for k in df.columns]) f = df.index.values @@ -196,7 +193,7 @@ def pna_csv_2_ntwks2(filename, *args, **kwargs): df[f'{param} (IMAG)'].values, ) - ntwk_dict[param] = Network(f=f, s=s, name=param, comments=comments, f_unit=f_unit) + ntwk_dict[param] = Network(f=f, s=s, name=param, comments=comments, f_unit='Hz') try: @@ -228,7 +225,7 @@ def pna_csv_2_ntwks3(filename): """ - header, comments, d, f_unit = read_pna_csv(filename) + header, comments, d = read_pna_csv(filename) col_headers = pna_csv_header_split(filename) # set impedance to 50 Ohm (doesn't matter for now) @@ -253,7 +250,7 @@ def pna_csv_2_ntwks3(filename): elif 's22' in h.lower() and 'db' in h.lower(): s[:,1,1] = mf.dbdeg_2_reim(d[:,k+1], d[:,k+2]) - n = Network(f=f,s=s,z0=z0, name = name, f_unit=f_unit) + n = Network(f=f,s=s,z0=z0, name = name, f_unit="Hz") return n else: @@ -579,7 +576,7 @@ def pna_csv_header_split(filename): list of column names """ warn("deprecated", DeprecationWarning, stacklevel=2) - header, comments, d, f_unit = read_pna_csv(filename) + header, comments, d = read_pna_csv(filename) n_traces = d.shape[1] - 1 # because theres is one frequency column @@ -626,7 +623,7 @@ def pna_csv_2_ntwks(filename): """ warn("deprecated", DeprecationWarning, stacklevel=2) #TODO: check the data's format (Real-imag or db/angle , ..) - header, comments, d, f_unit = read_pna_csv(filename) + header, comments, d = read_pna_csv(filename) #import pdb;pdb.set_trace() names = pna_csv_header_split(filename) @@ -642,7 +639,7 @@ def pna_csv_2_ntwks(filename): else: raise (NotImplementedError) name = os.path.splitext(os.path.basename(filename))[0] - return Network(f=f, s=s, name=name, comments=comments, f_unit=f_unit) + return Network(f=f, s=s, name=name, comments=comments, f_unit='Hz') else: for k in range(int((d.shape[1]-1)/2)): f = d[:,0] @@ -657,17 +654,17 @@ def pna_csv_2_ntwks(filename): s = d[:,k*2+1]+1j*d[:,k*2+2] ntwk_list.append( - Network(f=f, s=s, name=name, comments=comments, f_unit=f_unit) + Network(f=f, s=s, name=name, comments=comments, f_unit='Hz') ) return ntwk_list def pna_csv_2_freq(filename): warn("deprecated", DeprecationWarning, stacklevel=2) - header, comments, d, f_unit = read_pna_csv(filename) + header, comments, d = read_pna_csv(filename) f = d[:,0] - return Frequency.from_f(f, unit = f_unit) + return Frequency.from_f(f, unit = "Hz") def pna_csv_2_scalar_ntwks(filename, *args, **kwargs): @@ -688,14 +685,14 @@ def pna_csv_2_scalar_ntwks(filename, *args, **kwargs): """ warn("deprecated", DeprecationWarning, stacklevel=2) - header, comments, d, f_unit = read_pna_csv(filename) + header, comments, d = read_pna_csv(filename) n_traces = d.shape[1] - 1 # because theres is one frequency column cols = pna_csv_header_split(filename) f = d[:,0] - freq = Frequency.from_f(f, unit = f_unit) + freq = Frequency.from_f(f, unit = 'Hz') # loop through columns and create a single network for each column ntwk_list = [] From 41d5963ac86feabfe10a9294b9bb3ef9489b58e5 Mon Sep 17 00:00:00 2001 From: Lansus Date: Wed, 20 Nov 2024 10:28:40 +0800 Subject: [PATCH 55/58] Update unit test case. --- skrf/io/csv.py | 2 +- skrf/io/tests/test_csv.py | 3 +-- 2 files changed, 2 insertions(+), 3 deletions(-) diff --git a/skrf/io/csv.py b/skrf/io/csv.py index 4b892331d..9056ce7a2 100644 --- a/skrf/io/csv.py +++ b/skrf/io/csv.py @@ -144,7 +144,7 @@ def read_pna_csv(filename, *args, **kwargs) -> tuple[str, str, np.ndarray]: try: unit_tmp = unit_raw.lower() if unit_tmp in units_dict: - data[:, 0] *= units_dict[unit_tmp] + data[:, 0] *= FREQ_UNITS[units_dict[unit_tmp]] except Exception as exc: raise ValueError(f"Could not parse frequency unit '{unit_raw}'") from exc diff --git a/skrf/io/tests/test_csv.py b/skrf/io/tests/test_csv.py index 017ed8e7f..341cd0497 100644 --- a/skrf/io/tests/test_csv.py +++ b/skrf/io/tests/test_csv.py @@ -72,11 +72,10 @@ def test_read_pna_csv(self): """ with warnings.catch_warnings(record=True) as w: with self.assertWarns(DeprecationWarning): - header, comment, data, f_unit = rf.read_pna_csv(self.filename) + header, comment, data = rf.read_pna_csv(self.filename) self.assertEqual(header, 'Freq(Hz),"A,1"(REAL),"A,1"(IMAG),"R1,1"(REAL),"R1,1"(IMAG)') self.assertEqual(comment, 'this is a comment\nline\n') self.assertTrue((data == np.array([[750000000000, 1, 2, 3, 4], [1100000000000, 5, 6, 7,8], ])).all()) - self.assertEqual(f_unit, 'Hz') From 9dd635363589bd69b7735367d8f82c7088f99375 Mon Sep 17 00:00:00 2001 From: Julien Hillairet Date: Sun, 1 Dec 2024 19:28:26 +0100 Subject: [PATCH 56/58] Bump to version 1.5.0 --- conda.recipe/meta.yaml | 2 +- skrf/__init__.py | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/conda.recipe/meta.yaml b/conda.recipe/meta.yaml index 9865db289..a6b6a3d5f 100644 --- a/conda.recipe/meta.yaml +++ b/conda.recipe/meta.yaml @@ -1,5 +1,5 @@ {% set name = "scikit-rf" %} -{% set version = "1.4.1" %} +{% set version = "1.5.0" %} {% set hash_val = "8223204281599ba14b685d7e28b7e361" %} package: diff --git a/skrf/__init__.py b/skrf/__init__.py index 49ccb2f7e..2ede8dca7 100644 --- a/skrf/__init__.py +++ b/skrf/__init__.py @@ -3,7 +3,7 @@ implemented in Python. """ -__version__ = '1.4.1' +__version__ = '1.5.0' ## Import all module names for coherent reference of name-space #import io From 007dbb508cbf1f0eabca63858e637d2256c98807 Mon Sep 17 00:00:00 2001 From: Julien Hillairet Date: Sun, 1 Dec 2024 20:09:18 +0100 Subject: [PATCH 57/58] Ignore UP031 for the moment ruff v0.8 is now picky with the of percent format and throw error format specifiers are not used instead. --- pyproject.toml | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/pyproject.toml b/pyproject.toml index 77b5f4563..c38163c54 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -133,7 +133,8 @@ select = ["B", "E", "F", "FA", "I", "NPY", "UP", "W"] # Ignore some rules for all files # E741: ambiguous-variable-name -ignore = ["E741"] +# UP031: Use format specifiers instead of percent format +ignore = ["E741", "UP031"] [tool.ruff.lint.per-file-ignores] # Ignore some rules for some specific files From 9e8fd3b8d8f73e32d7a85cd2c46e7694ba387d84 Mon Sep 17 00:00:00 2001 From: Julien Hillairet Date: Mon, 2 Dec 2024 21:41:25 +0100 Subject: [PATCH 58/58] Publish releases on PyPI automatically --- .github/workflows/publish.yml | 35 +++++++++++++++++++++++++++++++++++ 1 file changed, 35 insertions(+) create mode 100644 .github/workflows/publish.yml diff --git a/.github/workflows/publish.yml b/.github/workflows/publish.yml new file mode 100644 index 000000000..02b4b4591 --- /dev/null +++ b/.github/workflows/publish.yml @@ -0,0 +1,35 @@ +# This workflows will upload a Python Package using uv when a release is created +# reference: +# https://github.com/astral-sh/trusted-publishing-examples/blob/main/.github/workflows/release.yml + +name: Publish on PyPI + +on: + release: + types: + - created + +jobs: + build_and_deploy: + + runs-on: ubuntu-latest + + permissions: + # IMPORTANT: this permission is mandatory for trusted publishing + id-token: write + + steps: + - name: Checkout Project + uses: actions/checkout@v4 + + - name: Install uv + uses: astral-sh/setup-uv@v3 + + - name: Set Up Python + run: uv python install + + - name: Build Package + run: uv build + + - name: Publish to PyPi + run: uv publish --trusted-publishing always