Skip to content

Instantly share code, notes, and snippets.

@maxanier
Created April 6, 2023 12:53
Show Gist options
  • Save maxanier/5a20c7554f22973340466d6b1351a1ef to your computer and use it in GitHub Desktop.
Save maxanier/5a20c7554f22973340466d6b1351a1ef to your computer and use it in GitHub Desktop.
De-embeding/Extracting Passives - Scikit-RF - Library/Playground
Display the source blob
Display the rendered blob
Raw
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
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)))
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