87 lines
2.4 KiB
Python
87 lines
2.4 KiB
Python
|
import numpy
|
||
|
from numpy import sqrt, real, abs
|
||
|
from numpy.linalg import pinv
|
||
|
|
||
|
|
||
|
def diag(twod):
|
||
|
# numpy.diag() but makes a stack of diagonal matrices
|
||
|
return numpy.einsum('ij,jk->ijk', twod, numpy.eye(twod.shape[-1], dtype=twod.dtype))
|
||
|
|
||
|
|
||
|
def s2z(s, zref):
|
||
|
# G0_inv @ inv(I - S) @ (S Z0 + Z0*) @ G0
|
||
|
# Where Z0 is diag(zref) and G0 = diag(1/sqrt(abs(real(zref))))
|
||
|
nf = s.shape[-1]
|
||
|
I = numpy.eye(nf)[None, :, :]
|
||
|
zref = numpy.array(zref, copy=False)
|
||
|
gref = 1 / sqrt(abs(zref.real))
|
||
|
z = diag(1 / gref) @ pinv(I - s) @ ( S @ diag(zref) + diag(zref).conj()) @ diag(gref)
|
||
|
return z
|
||
|
|
||
|
|
||
|
def change_of_zref(
|
||
|
s, # (nf, np, np)
|
||
|
zref_old, # (nf, np)
|
||
|
zref_new, # (nf, np)
|
||
|
):
|
||
|
# Change-of-Z0 to Z0'
|
||
|
# S' = inv(A) @ (S - rho*) @ inv(I - rho @ S) @ A*
|
||
|
# A = inv(G0') @ G0 @ inv(I - rho*) (diagonal)
|
||
|
# rho = (Z0' - Z0) @ inv(Z0' + Z0) (diagonal)
|
||
|
|
||
|
I = numpy.eye(SL.shape[-1])[None, :, :]
|
||
|
zref_old = numpy.array(zref_old, copy=False)
|
||
|
zref_new = numpy.array(zref_new, copy=False)
|
||
|
|
||
|
g_old = 1 / sqrt(abs(zref_old.real))
|
||
|
r_new = sqrt(abs(zref_new.real))
|
||
|
|
||
|
rhov = (zref_new - zref_old) / (zref_new + zref_old)
|
||
|
av = r_new * g_old / (1 - rhov.conj())
|
||
|
|
||
|
s_new = diag(1 / av) @ (s - diag(rhov.conj())) @ pinv(I[None, :] - diag(rhov) @ S) @ diag(av.conj())
|
||
|
return s_new
|
||
|
|
||
|
|
||
|
def embedding(
|
||
|
See, # (nf, ne, ne)
|
||
|
Sei, # (nf, ne, ni)
|
||
|
Sie, # (nf, ni, ne)
|
||
|
Sii, # (nf, ni, ni)
|
||
|
SL, # (nf, ni, ni)
|
||
|
):
|
||
|
# Reveyrand, doi:10.1109/INMMIC.2018.8430023
|
||
|
I = numpy.eye(SL.shape[-1])[None, :, :]
|
||
|
S_tot = See + Sei @ pinv(I - SL @ Sii) @ SL @ Sie
|
||
|
return S_tot
|
||
|
|
||
|
def deembedding(
|
||
|
Sei, # (nf, ne, ni)
|
||
|
Sie, # (nf, ni, ne)
|
||
|
Sext, # (nf, ne, ne)
|
||
|
See, # (nf, ne, ne)
|
||
|
Si, # (nf, ni, ni)
|
||
|
):
|
||
|
# Reveyrand, doi:10.1109/INMMIC.2018.8430023
|
||
|
# Requires balanced number of ports, similar to VNA calibration
|
||
|
Sei_inv = pinv(Sei)
|
||
|
Sdif = Sext - See
|
||
|
return Sei_inv @ Sdif @ pinv(Sie + Sii @ Sei_inv @ Sdif)
|
||
|
|
||
|
|
||
|
def thru_with_Zref_change(
|
||
|
zref0, # (nf,)
|
||
|
zref1, # (nf,)
|
||
|
):
|
||
|
nf = zref0.shape[0]
|
||
|
s = numpy.empty((nf, 2, 2), dtype=complex)
|
||
|
s[:, 0, 0] = zref1 - zref0
|
||
|
s[:, 0, 1] = 2 * sqrt(zref0 * zref1)
|
||
|
s[:, 1, 0] = s[:, 0, 1]
|
||
|
s[:, 1, 1] = -s[:, 0, 0]
|
||
|
|
||
|
s /= zref0 + zref1
|
||
|
return s
|
||
|
|
||
|
|