Created
April 6, 2023 12:53
-
-
Save maxanier/5a20c7554f22973340466d6b1351a1ef to your computer and use it in GitHub Desktop.
De-embeding/Extracting Passives - Scikit-RF - Library/Playground
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
from skrf import Network, Frequency | |
import skrf as rf | |
import numpy as np | |
import scipy.linalg as sp_alg | |
from skrf.calibration import SplitPi, SplitTee | |
class ManualS2P: | |
"""Collection of functions dealing with single frequency 2-port scattering parameters represented by np.matrix""" | |
def s2t(S_source: np.matrix) -> np.matrix: | |
"""Convert single S-parameter matrix to (Scattering) Transfer matrix.""" | |
#https://en.wikipedia.org/wiki/Scattering_parameters#Scattering_transfer_parameters | |
S_source_det= np.linalg.det(S_source) | |
return np.matrix([[-S_source_det/S_source[1,0] , S_source[0,0]/S_source[1,0]],[-S_source[1,1]/S_source[1,0] , 1/S_source[1,0]]] ) | |
def chop_in_half_t(T_combined: np.matrix) -> np.matrix: | |
"""Takes symmetric recipocal (Scattering) Transfer matrix and splits it at the virtual symmetry (in the center)""" | |
return sp_alg.sqrtm(T_combined) | |
def deembed_t(T_combined: np.matrix, T_pre: np.matrix, T_post: np.matrix) -> np.matrix: | |
"""Takes (Scattering) Transfer matrix and removes given parts from input and output""" | |
return np.dot(np.dot(np.linalg.inv(T_pre) , T_combined), np.linalg.inv(T_post)) | |
def t2s(T_source: np.matrix) -> np.matrix: | |
"""Convert single (Scattering) Transfer matrix to S-parameter matrix.""" | |
#https://en.wikipedia.org/wiki/Scattering_parameters#Scattering_transfer_parameters | |
T_source_det = np.linalg.det(T_source) | |
return np.matrix([[T_source[0,1]/T_source[1,1], T_source_det/T_source[1,1]] , [ 1/T_source[1,1] , - T_source[1,0] / T_source[1,1]]]) | |
class NetworkS2P: | |
"""Collection of functions dealing with 2-port scattering parameters over frequency represented with scikit-rf """ | |
def deembed_T(combined: Network, thru: Network) -> Network: | |
"""Deembeds network | |
Given a 2-port network that is connected using an identical/symmetrical passive structure at in- and output | |
and the 2-port network of that combined in- and output structure, the original network is deembeded. | |
Assuming T_thru = T_half * T_half | |
Assuming T_combined = T_half * T_dut * T_half | |
De-embedding: T_dut = T_half^{-1} * T_half * T_dut * T_half * T_half^{-1} | |
Uses skrf magic. | |
""" | |
thru_half = rf.network.chopinhalf(thru) | |
return thru_half.inv ** combined ** thru_half.inv | |
def chop_in_half_manual(full: Network) -> Network: | |
"""Splits a passive network in the virtual center. | |
Network is assumed to formed of two symmetrical parts and must be reciprocal. | |
""" | |
# Manual version of https://scikit-rf.readthedocs.io/en/v0.18.1/api/generated/skrf.network.chopinhalf.html | |
half_t = np.array([sp_alg.sqrtm(np.matrix(x)) for x in full.t[:]]) | |
half_s = rf.t2s(half_t) | |
return Network(frequency=full.f/1e9, s=half_s) | |
def deembed_T_manual(combined: Network, thru: Network) -> Network: | |
"""Deembeds network | |
Given a 2-port network that is connected using an identical/symmetrical passive structure at in- and output | |
and the 2-port network of that combined in- and output structure, the original network is deembeded. | |
Assuming T_thru = T_half * T_half | |
Assuming T_combined = T_half * T_dut * T_half | |
De-embedding: T_dut = T_half^{-1} * T_half * T_dut * T_half * T_half^{-1} | |
Manual version of #deembed (which uses skrf) | |
""" | |
thru_half = NetworkS2P.chop_in_half_manual(thru) | |
deembeded_t = np.array([ManualS2P.deembed_t(combined.t[i],thru_half.t[i],thru_half.t[i]) for i in range(combined.t[:,0,0].size) ]) | |
deembeded_s = rf.t2s(deembeded_t) | |
return Network(frequency=combined.f/1e9, s=deembeded_s) | |
def deembed_wang2022(combined: Network, thru: Network) -> Network: | |
"""De-embeds network based on Wang2022 approach | |
https://doi.org/10.1109/LMWC.2022.3173970 | |
Intended to remove a series-shunt fixture. | |
Based on ABCD parameters | |
Assuming A_thru = A_left * A_right | |
Assuming A_combined = A_left * A_dut * A_right | |
De-embedding A_dut = A_left^{-1} * A_left * A_dut * A_right * A_right^{-1} | |
Originally, the proposed method requires an ideal length transmission line between pad and DUT, but results are ok even with a different length | |
""" | |
wang_A = [] | |
for i in range(len(combined.f)): | |
x = thru.a[i] | |
k = x[0,0]**2 - x[0,1]*x[1,0] | |
a = (x[0,0]+np.sqrt(k)) * np.sqrt((x[0,0]-np.sqrt(k))/(2*x[0,1]*x[1,0])) | |
b = np.sqrt(x[0,1]*(x[0,0]-np.sqrt(k))/2/x[1,0]) | |
c = np.sqrt(x[1,0]*(x[0,0]-np.sqrt(k))/2/x[0,1]) | |
At = [[a,b],[c,a]] | |
At_inv = np.linalg.inv(At) | |
A = np.dot(np.dot(At_inv, combined.a[i]),At_inv) | |
wang_A.append(A) | |
return Network(frequency=combined.f/1e9, s=rf.a2s(np.array(wang_A))) | |
def deembed_ito2008(combined: Network, thru: Network) -> Network: | |
"""De-embeds network based on Ito2008 approach. Doesn't seem to work | |
https://doi.org/10.1109/MWSYM.2008.4633183 | |
Splits thru into left and right based on Y parameters and de-embeds based on T parameters. | |
Assuming Y_half = ? | |
Assuming T_combined = T_half * T_dut * T_half | |
De-embedding: T_dut = T_half^{-1} * T_half * T_dut * T_half * T_half^{-1} | |
""" | |
ito_y_left = np.array([[ [x[0,0] - x[1,0] , 2*x[1,0]], [2*x[1,0], -2*x[1,0]] ] for x in thru.y[:]]) | |
ito_y_right = np.array([[ [-2*x[0,1] , 2*x[0,1]], [2*x[0,1], x[1,1] - x[0,1]]] for x in thru.y[:]]) | |
ito_left = Network(frequency=thru.f/1e9, s=rf.y2s(ito_y_left)) | |
ito_right = Network(frequency=thru.f/1e9, s=rf.y2s(ito_y_right)) | |
ito_left_t = rf.s2t(ito_left.s) | |
ito_right_t = rf.s2t(ito_right.s) | |
ito_t = [] | |
for i in range(len(combined.f)): | |
ito_t.append(ManualS2P.deembed_t(ito_left_t[i], combined.t[i], ito_right_t[i])) | |
ito_t = np.array(ito_t) | |
return Network(frequency=combined.f/1e9, s=rf.t2s(ito_t)) | |
def deembed_splitpi(combined: Network, thru: Network) -> Network: | |
"""De-embeds network based on Nan2007 approach. Implemented in scikit-rf | |
https://doi.org/10.1109/RFIC.2007.380889 | |
Intended to remove a shunt-series fixture. | |
Assuming A_left, Aright=? | |
Assuming A_combined = A_left * A_dut * A_right | |
De-embedding A_dut = A_left^{-1} * A_left * A_dut * A_right * A_right^{-1} | |
""" | |
return SplitPi(dummy_thru = thru).deembed(combined) | |
def deembed_splittee(combined: Network, thru: Network) -> Network: | |
"""De-embeds network based on Kobrinksi2005 approach. Implemented in scikit-rf | |
https://doi.org/10.1109/TADVP.2004.841672 | |
Intended to remove a series-shunt fixture. | |
Assuming A_left, Aright=? | |
Assuming A_combined = A_left * A_dut * A_right | |
De-embedding A_dut = A_left^{-1} * A_left * A_dut * A_right * A_right^{-1} | |
""" | |
return SplitTee(dummy_thru = thru).deembed(combined) | |
def deembed_splitpi_man(combined: Network, thru: Network) -> Network: | |
"""De-embeds network based on Nan2007 approach | |
https://doi.org/10.1109/RFIC.2007.380889 | |
Intended to remove a shunt-series fixture. | |
Assuming A_left = [[1,z],[y,1+y*z]], A_right = [[1+y*z, z],[y, 1]] | |
Extracting z and y from Y-Param assuming shunt-series-shunt thru. | |
Assuming A_combined = A_left * A_dut * A_right | |
De-embedding A_dut = A_left^{-1} * A_left * A_dut * A_right * A_right^{-1} | |
""" | |
nan_A = [] | |
for i in range(len(combined.f)): | |
x = thru.y[i] | |
y = x[0,0]+x[0,1] | |
z = - 1/2/x[0,1] | |
A_left = [[1,z],[y,1+y*z]] | |
A_right = [[1+y*z, z],[y, 1]] | |
A_left_inv = np.linalg.inv(A_left) | |
A_right_inv = np.linalg.inv(A_right) | |
A = np.dot(np.dot(A_left_inv, combined.a[i]),A_right_inv) | |
nan_A.append(A) | |
return Network(frequency=combined.f/1e9, s=rf.a2s(np.array(nan_A))) |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
import numpy as np | |
from matplotlib import pyplot as plt | |
from skrf import Network, Frequency | |
import skrf as rf | |
class InductorData: | |
"""Represents measured/simulated inductor data composed of L and Q over frequency""" | |
def __init__(self, f, L, Q, name="Inductor"): | |
self.f_min = np.min(f) | |
self.f_max = np.max(f) | |
self.f = f | |
self.L = L | |
self.Q = Q | |
self.name = name | |
if self.f[0] == 0: #Inductance at 0Hz does not make any sense and might be NaN | |
self.L[0] = 0 | |
def valueAt(self, f, Interpolate=True): | |
if f > self.f_max or f < self.f_min: | |
raise RuntimeError("Frequency out of bounds", f, self.f_max, self.f_min) | |
if Interpolate: | |
return [np.interp(f, self.f, self.L), np.interp(f, self.f, self.Q)] | |
else: | |
idx = (np.abs(self.f - f)).argmin() | |
return [self.L[idx] , self.Q[idx]] | |
def plot(self): | |
fig=plt.figure(figsize=(20,5)) | |
a1 = fig.add_axes([0,0,1,1]) | |
a1.set_ylim(0,np.max(self.L[1:])) | |
plt.title(self.name) | |
plt.xlabel("Frequency") | |
plt.ylabel("Inductance") | |
a1.plot(self.f, self.L) | |
a2 = a1.twinx() | |
a2.set_ylim(0,np.max(self.Q)) | |
a2.plot(self.f, self.Q) | |
def savetxt(self, filepath): | |
np.savetxt(filepath, np.transpose([self.f, self.L, self.Q]), delimiter=',',fmt='%.5e') | |
def calculateDiffIndWeird(Sind: Network, name="Differential Inductance"): | |
"""Accepts a scikit-rf 2 port network and calculates the differential inductance based on Okada2010 10.5772/8435""" | |
Sdiff = (Sind.s[:,0,0]-Sind.s[:,0,1]-Sind.s[:,1,0]+Sind.s[:,1,1])/2 | |
Zdiff = 2 * 50 * (1+Sdiff)/(1-Sdiff) | |
Ldiff = 1/2/np.pi/(Sind.f+1e-12)*np.imag(Zdiff) | |
Qdiff = np.imag(Zdiff)/np.real(Zdiff) | |
return InductorData(Sind.f, Ldiff, Qdiff, name) | |
def calculateInductance(Sind: Network, name="Inductor"): | |
"""Accepts a scikit-rf 2 port network and calculates inductance based on Y-parameter""" | |
ls = -np.imag(1/Sind.y[:,0,1])/2/3.14/Sind.f | |
q = np.imag(1/Sind.y[:,0,1])/np.real(1/Sind.y[:,0,1]) | |
return InductorData(Sind.f, ls, q, name) | |
class TLData: | |
"""Represents a transmission line over frequency | |
""" | |
def __init__(self, f, Zc, Att, name="CPW"): | |
""" | |
Att: attenuation in dB per meter | |
""" | |
self.f_min = np.min(f) | |
self.f_max = np.max(f) | |
self.f = f | |
self.Zc = Zc | |
self.Att = Att | |
self.name = name | |
def valueAt(self, f, Interpolate=True): | |
if f > self.f_max or f < self.f_min: | |
raise RuntimeError("Frequency out of bounds", f, self.f_max, self.f_min) | |
if Interpolate: | |
return [np.interp(f, self.f, self.Zc), np.interp(f, self.f, self.Att)] | |
else: | |
idx = (np.abs(self.f - f)).argmin() | |
return [self.Zc[idx] , self.Att[idx]] | |
def savetxt(self, filepath): | |
np.savetxt(filepath, np.transpose([self.f, self.Zc, self.Att]), delimiter=',',fmt='%.5e') | |
def plot(self): | |
fig=plt.figure(figsize=(20,5)) | |
a1 = fig.add_axes([0,0,1,1]) | |
a1.set_ylim(0,np.max(self.Zc[1:])) | |
plt.title(self.name) | |
plt.xlabel("Frequency") | |
plt.ylabel("Impedance") | |
a1.plot(self.f, self.Zc) | |
a2 = a1.twinx() | |
a2.set_ylim(0,np.max(self.Att)) | |
a2.plot(self.f, self.Att) | |
def fromSParam(Sind: Network, length, name="TL"): | |
zc = np.sqrt(Sind.z[:,0,0]/ Sind.y[:,0,0]) | |
gamma = 1/length * np.arctanh(1/zc/Sind.y[:,0,0]) | |
alpha = np.real(gamma) | |
att = 20*alpha*np.log10(np.exp(1)) | |
return TLData(Sind.f, np.abs(zc), att, name=name) | |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment