scikit-rf / scikit-rf

RF and Microwave Engineering Scikit
http://scikit-rf.org
BSD 3-Clause "New" or "Revised" License
725 stars 287 forks source link

Reflection sign in cpw.line(embed=True) is flipped #717

Closed ZiadHatab closed 2 years ago

ZiadHatab commented 2 years ago

Hi!

I was running some numerical tests with the CPW media object and I was using the method line(). As I wanted a mismatched line, I used the "embed=True" option. But when I checked the phase of S11 (or S22) of the resulted network, it comes with a flipped sign (180deg offset).

I did my own testing and implemented my own TL method using the equations in:

R. Marks and D. Williams, "A general waveguide circuit theory," Journal of Research (NIST JRES), National Institute of Standards and Technology, Gaithersburg, MD, no. 97, 1992. https://www.ncbi.nlm.nih.gov/pmc/articles/PMC4914227/

In all cases, I get the correct answer. Interestingly, when I don't use the "embed=True" option, and afterward just renormalize the network, then I get the same answer as in the paper. image

Below is my code. I think the problem is that the impedances are flipped in the line() method, because when I flip the impedances in my implementation, then I get the same result as in line(embed=True).

import skrf as rf
from skrf.media import CPW
import numpy as np
import matplotlib.pyplot as plt

def S2Z(S, z=50):
    # from eq. (74) in
    # R. Marks and D. Williams, "A general waveguide circuit theory," 
    # Journal of Research (NIST JRES), National Institute of Standards and Technology,
    # Gaithersburg, MD, no. 97, 1992.
    # https://www.ncbi.nlm.nih.gov/pmc/articles/PMC4914227/
    N = S.shape[0]
    z = np.atleast_1d(z)*np.ones(N) # make sure it is an array of z
    I = np.eye(N)
    inv = np.linalg.pinv
    Zref = np.diag(z)
    U    = np.diag(np.sqrt(z.real)/abs(z))  # assume v0 = 1
    Uinv = np.diag(abs(z)/np.sqrt(z.real))
    return inv(I - Uinv@S@U)@(I + Uinv@S@U)@Zref

def Z2S(Z, z=50):
    # from eq. (73) in
    # R. Marks and D. Williams, "A general waveguide circuit theory," 
    # Journal of Research (NIST JRES), National Institute of Standards and Technology,
    # Gaithersburg, MD, no. 97, 1992.
    # https://www.ncbi.nlm.nih.gov/pmc/articles/PMC4914227/
    N = Z.shape[0]
    z = np.atleast_1d(z)*np.ones(N) # make sure it is an array of z
    inv = np.linalg.pinv
    Zref = np.diag(z)
    U    = np.diag(np.sqrt(z.real)/abs(z))  # assume v0 = 1
    Uinv = np.diag(abs(z)/np.sqrt(z.real))
    return U@(Z-Zref)@inv(Z+Zref)@Uinv

def S2T(S):
    T = S.copy()
    T[0,0] = -(S[0,0]*S[1,1]-S[0,1]*S[1,0])
    T[0,1] = S[0,0]
    T[1,0] = -S[1,1]
    T[1,1] = 1
    return T/S[1,0]

def T2S(T):
    S = T.copy()
    S[0,0] = T[0,1]
    S[0,1] = T[0,0]*T[1,1]-T[0,1]*T[1,0]
    S[1,0] = 1
    S[1,1] = -T[1,0]
    return S/T[1,1]

def Qnm(Zn, Zm):
    # Impedance transformer in T-paramters from on Eqs. (86) and (87) in
    # R. Marks and D. Williams, "A general waveguide circuit theory," 
    # Journal of Research (NIST JRES), National Institute of Standards and Technology,
    # Gaithersburg, MD, no. 97, 1992.
    # https://www.ncbi.nlm.nih.gov/pmc/articles/PMC4914227/
    Gnm = (Zm-Zn)/(Zm+Zn)
    return np.sqrt(Zn.real/Zm.real*(Zm/Zn).conjugate())/np.sqrt(1-Gnm**2)*np.array([[1, Gnm],[Gnm, 1]])

def TL_from_T2S(l, cpw, Z01=None, Z02=None):
    # create mismatched transmission line from skrf cpw object
    N = len(cpw.Z0)  # number of frequency points
    Z01 = cpw.Z0 if Z01 is None else np.atleast_1d(Z01)*np.ones(N)
    Z02 = Z01 if Z02 is None else np.atleast_1d(Z02)*np.ones(N)
    S = []
    for g,zc,z01,z02 in zip(cpw.gamma, cpw.Z0, Z01, Z02):
        T = Qnm(z01,zc)@np.diag([np.exp(-l*g), np.exp(l*g)])@Qnm(zc,z02)
        S.append(T2S(T))

    return rf.Network(s=np.array(S), frequency=cpw.frequency, name='From T2S')

def TL_from_Z2S(l, cpw, Z01=None, Z02=None):
    # create mismatched transmission line from skrf cpw object
    N = len(cpw.Z0)  # number of frequency points
    Z01 = cpw.Z0 if Z01 is None else np.atleast_1d(Z01)*np.ones(N)
    Z02 = Z01 if Z02 is None else np.atleast_1d(Z02)*np.ones(N)
    S = []
    P = np.array([[0,1],[1,0]])
    for g,zc,z01,z02 in zip(cpw.gamma, cpw.Z0, Z01, Z02):
        Sline = np.exp(-l*g)*P
        Z = S2Z(Sline, zc)
        S.append(Z2S(Z,[z01,z02]))

    return rf.Network(s=np.array(S), frequency=cpw.frequency, name='From Z2S')

if __name__=='__main__':
    freq = rf.F(0.5, 150, 300, unit='GHz')
    cpw = CPW(freq, w=40e-6, s=25e-6, ep_r=12*(1-0.001j), t=5e-6, rho=2e-8)

    z0 = 20  # the new impedance 
    length = 1e-3
    from_skrf  = cpw.line(length, 'm', z0=z0, embed=True, s_def='pseudo')
    from_skrf2 = cpw.line(length, 'm')
    from_skrf2.renormalize(z0, s_def='pseudo')
    from_t2s   = TL_from_T2S(length, cpw, z0)
    from_z2s   = TL_from_Z2S(length, cpw, z0)

    plt.figure()
    from_skrf.s11.plot_s_deg_unwrap(label='S11 from skrf...line(embed=True)', 
                                    marker='o', markevery=20, markersize=8)
    from_skrf2.s11.plot_s_deg_unwrap(label='S11 from skrf...renormalize()', 
                                    marker='s', markevery=20, markersize=8)
    from_t2s.s11.plot_s_deg_unwrap(label='S11 from T2S, NIST', 
                                   marker='^', markevery=20, markersize=8)
    from_z2s.s11.plot_s_deg_unwrap(label='S11 from Z2S, NIST', 
                                   marker='v', markevery=20, markersize=8)
    plt.title('Phase of S11 from mismatched line')
    plt.ylim([-360, 360])

    plt.show()

# EOF
ZiadHatab commented 2 years ago

I just read through the thread https://github.com/scikit-rf/scikit-rf/issues/651 I think it addresses a similar question as mine. From what I understood, in newer release the impedances will be flipped? correct me if I'm wrong.

mhuser commented 2 years ago

Hello @ZiadHatab , Thank you a lot for your interest in CPW media and reporting this ! Can you try this : from_skrf = cpw.line(length, 'm', z0=cpw.Z0, embed=True) ?

mhuser commented 2 years ago

I just read through the thread #651 I think it addresses a similar question as mine. From what I understood, in newer release the impedances will be flipped? correct me if I'm wrong.

We are looking forward to changing the behaviour described in #651, hopefully in the next release following the upcoming v0.23.0.

715 will issue a deprecation warning on the embed parameter. The work has already started here #701 but there is some precaution to take to not break everything.

The new behaviour will be like this: if there is a media or line z0, the network port impedance will be Z0 renormalized to z0 (without the wrong impedance flip), else if there is none z0, the network will have Z0 for port impedance (again without the flip).

We did not find an agreement yet however to change z0 and Z0 names that are very error prone.

ZiadHatab commented 2 years ago

@mhuser

Thanks for the hint. I don't think writing cpw.line(length, 'm', z0=cpw.Z0, embed=True) would do anything, as I'm embedding the line in its own characteristic impedance, because I didn't define a port impedance when constructing cpw (see my code above).

In any case, I think I understand now how the CPW media works. I need to define my mismatch impedance as the port impedance in the main object CPW. Then, when I create a line I should set z0 to the characteristic impedance (similar to what you wrote). So, to get a correct mismatched line I would need to write:

z0_new = 20
cpw = CPW(freq, w=40e-6, s=25e-6, ep_r=12*(1-0.001j), t=5e-6, rho=2e-8, z0=z0_new)
from_skrf = cpw.line(length, 'm', z0=cpw.Z0, embed=True)

Now, I'm going to be honest with you. This whole thing with port impedance in the CPW object makes no sense to me. Why even including it to begin with? I mean, a transmission should be defined, by default, only by its characteristic impedance. After that, the user can do whatever he wants, to renormalize it to whatever he desire. So, for me something like this makes more sense:

cpw = CPW(freq, w=40e-6, s=25e-6, ep_r=12*(1-0.001j), t=5e-6, rho=2e-8)  # no mention of Z0 nor z0
matched_line = cpw.line(1e-3, 'm')
mismatched_line = cpw.line(1e-3, 'm').renormalize(z0_new)  # the ports are now set to z0_new

I think a transmission line should only be defined by its characteristic impedance, and it is the responsibility of the user to renormalize it to create a mismatch line or what ever he wants. I say this, because I work a lot with EM simulations as HFSS, and there when you define a wave port, you always get the results referenced to characteristic impedance of the port. After that you can renormalize it to whatever you want. The same goes with (multiline) TRL calibration, by default your reference impedance is the characteristic impedance of the lines, after that you renormalize to whatever you want.

So, what I'm trying to say is: just deprecate both Z0 and z0 from transmission line media, and put a message to the user that they need to use .renormalize() if they want their network to be in a different impedance than the characteristic impedance of the line. Of course, Z0 (characteristic impedance) should remain as a property of the CPW object, similar to the effective epsilon and gamma.

Anyways, thanks for the clarification :)

mhuser commented 2 years ago

Now, I'm going to be honest with you. This whole thing with port impedance in the CPW object makes no sense to me. Why even including it to begin with? I mean, a transmission should be defined, by default, only by its characteristic impedance. After that, the user can do whatever he wants, to renormalize it to whatever he desire. So, for me something like this makes more sense:

Thank you a lot for your feedback and honesty @ZiadHatab :) Your understanding sounds correct. Currently the embed parameter almost does not works at all outside defining z0 at media initialization and with the z0=media.Z0 trick. This could even be worse as you think: not only CPW but all media inherit from this behaviour.

I think this come from the past and from a kind of misusage with some users wanting to define an arbitrary impedance line quick (but without using an appropriated media such as DefinedGammaZ0) and others wanting to see "the same line as if measured on a 50 ohm VNA". However, having only the raw Z0 line would be much simpler.