Introduction

In this notebook we look for the possibility to use the EAST B-port antenna front face, made of 2x2 straps (the loading on a dielectric medium example is evaluated from HFSS), in an ELM-resilient electrical layout. The ELM resilient is achieved here by using a complex conjugated T-junction connected to poloidal pairs of straps.

First of all, we make here the preliminary Python packages importation.


In [1]:
import sys
sys.path.append('/Linux/skrf_ref/scikit-rf/') # add the directory to Python PATH
import skrf as rf

EAST B-port front face model import

In the above, we import from HFSS modeling the EAST B-port front face, from the Touchstone HFSS export


In [2]:
# front face ports characteristic impedance [Ohm]
front_face_Z0 = 2.472487E+001 

# Matching impedance
# Impedance seens at the T junction when matched [Ohm]
T_Z0 = 40

# characteristic impedance of the T junction feeding port. [Ohm]
Z_in = 4

In [3]:
front_face_touchstone_file = 'C:\Users\JH218595\Documents\Notebooks\WEST\ICRH\EAST\EAST_ICRH_BportAntenna_EAST_ICRH_BportAntenna_2.s4p'
front_face = rf.io.hfss_touchstone_2_network(front_face_touchstone_file, f_unit='MHz')
# The characteristic impedance in the touchstone exportation was normalized to 50 Ohm  
# Here we renormalize it to the port characteristic impedance
front_face.renormalize(z_new=front_face_Z0)

front_face


Out[3]:
4-Port Network: 'EAST_ICRH_BportAntenna_EAST_ICRH_BportAntenna_2',  20-60 MHz, 81 pts, z0=[ 24.72487+0.j  24.72487+0.j  24.72487+0.j  24.72487+0.j]

For consistency with the previous data, below we define the global frequency bandwith on which we will work in next sections:


In [4]:
frequency = front_face.frequency
frequency


Out[4]:
20-60 MHz, 81 pts

Electrical layout

Capacitors

The matching of the resonant double loop (RDL) is performed here by capacitors. In a preliminary approach, these capacitors are modelled by a self inductance $L_C$ in serie with a capacitance $C$. The resulting circuit is expressed as a two port network given by the following function.


In [5]:
def capacitor_to_network(freq, C, L=25e-9, Z0=50):
    '''
    Create a 2 ports-network consisting of a serie capacitance and inductance
    
    Inputs:
        f : skrf.Frequency
        C : capacitance in Farad
        L : inductance in Henry (default : 25 nH)
        Z0: characteristic impedance in Ohm (default: 50 Ohm)
    Returns:
        skrf.Network 
    '''
    S_capacitor = np.zeros((freq.npoints, 2, 2), dtype='complex')
    
    for idf,f in enumerate(freq.f):
        Z_C_serie = 1.0 / (1j*C*2*np.pi*f)
        Z_L_serie = 1j*L*2*np.pi*f
        Z_R_serie = 0e-9
        Z_capacitor = Z_R_serie + Z_C_serie + Z_L_serie
    
        # serie-thru impedance S-matrix
        S_capacitor[idf,] = 1/(Z_capacitor+2*Z0)*np.array([[Z_capacitor,2*Z0],[2*Z0, Z_capacitor]])
        
    return(rf.Network(frequency=freq, s=S_capacitor, z0=Z0))

T-junction

Above we define an ideal T-junction (bridge), with Zin as input characteristic impedance, and the front face characteristic impedance for the output ports. [Cf other notebook for demonstration]


In [6]:
def Z_par(Zj, Zk):
    return( Zj*Zk / (Zj + Zk))

def Sii(Zi, Zj, Zk):
    Zjk = Z_par(Zj, Zk)
    Sii = (Zjk - Zi)/(Zjk + Zi)
    return(Sii)
    
def Sji(Zi, Zj, Zk):
    Zjk = Z_par(Zj, Zk)
    Sji = sqrt(Zi)/sqrt(Zj)*2*Zjk / (Zjk + Zi) 
    return(Sji)
    
def get_s_bridge(Z0,Z1,Z2):
    S = np.zeros((3,3))
    S[0,0] = Sii(Z0, Z1, Z2)
    S[1,1] = Sii(Z1, Z2, Z0)
    S[2,2] = Sii(Z2, Z0, Z1)
    
    S[1,0] = Sji(Z0, Z1, Z2)
    S[2,0] = Sji(Z0, Z2, Z1)
    S[2,1] = Sji(Z1, Z2, Z0)
    S[0,1] = Sji(Z1, Z0, Z2)
    S[0,2] = Sji(Z2, Z0, Z1)
    S[1,2] = Sji(Z2, Z1, Z0)
    return(S)

S_bridge_ideal = get_s_bridge(Z_in, front_face_Z0, front_face_Z0)

In [7]:
S_bridge_ideal


Out[7]:
array([[ 0.51107522,  0.60778373,  0.60778373],
       [ 0.60778373, -0.75553761,  0.24446239],
       [ 0.60778373,  0.24446239, -0.75553761]])

In [8]:
# Passivity check
np.allclose(np.eye(3), np.dot(S_bridge_ideal, np.transpose(np.conjugate(S_bridge_ideal))))


Out[8]:
True

In [9]:
# Creating a rf.Network from the scattering matrix
# the ideal S matrix is repeated fx3x3
S = S_bridge_ideal.reshape(1,3,3).repeat(frequency.npoints,axis=0)
bridge = rf.Network(frequency=frequency, s=S, z0=[Z_in, front_face_Z0, front_face_Z0])
bridge


Out[9]:
3-Port Network: '',  20-60 MHz, 81 pts, z0=[  4.00000+0.j  24.72487+0.j  24.72487+0.j]

Capacitors + T-junction

The above function connect two matching capacitors to the T-junction. The resulting is a 3-port network, with port#1 as the feeding port. Port 2 and 3 are to be connected to the straps.


In [10]:
def matching_unit(freq, bridge, C1, C2):
    '''
    Connect the two matching units to the loaded front face
    '''
    capa_H = capacitor_to_network(freq, C=C1, Z0=front_face_Z0) # Z0 to match HFSS export
    capa_B = capacitor_to_network(freq, C=C2, Z0=front_face_Z0)
    return(rf.connect(rf.connect(bridge,1, capa_H,0),2, capa_B,0))

In [11]:
matching_unit1 = matching_unit(frequency, bridge, 50e-12, 100e-12)
matching_unit1.name = 'matching unit'
matching_unit1


Out[11]:
3-Port Network: 'matching unit',  20-60 MHz, 81 pts, z0=[  4.00000+0.j  24.72487+0.j  24.72487+0.j]

Front Face + Capacitor + T-junction

Here we connect the front face to two toroidally adjacent matching units. The resulting is a two port network, corresponding to the two feeding lines (ie. from generators).


In [12]:
def antenna_network(C, freq, bridge, front_face):
    '''
    Returns the 2 ports network representing the loaded antenna 
    for a given capacitors set C1,C2,C3,C4. 
    
    C1 and C3 are paired
    C2 and C4 are paired 
    
    Inputs
        freq: rf.Frequency
        C : [C1,C2,C3,C4] capacitor values in farad
    Returns
        rf.Network
    '''
    matching_unit_l = matching_unit(freq, bridge, C[0], C[2])
    matching_unit_r = matching_unit(freq, bridge, C[1], C[3])
    
    # ports 0 & 2 of the front face are connected to port 1 and 2 of the matching unit
    # ports 1 & 3 of the front face are connected to port 1 and 2 of the matching unit  
    temp = rf.innerconnect(rf.connect(front_face, 0, matching_unit_l, 1), 1, 4) 
    network = rf.innerconnect(rf.connect(temp, 0, matching_unit_r, 1), 0, 3)
    network.name = 'antenna'
    return(network)

Below we construct an antenna and set up with arbitrary capacitor values. Then plot the S11 and S22.


In [13]:
import scipy
C = 50e-12 + scipy.random.rand(4)*(300e-12 - 50e-12)
print(C)
antenna = antenna_network(C, frequency, bridge, front_face)
antenna


[  2.86998818e-10   2.45706114e-10   1.57958927e-10   5.96029002e-11]
Out[13]:
2-Port Network: 'antenna',  20-60 MHz, 81 pts, z0=[ 4.+0.j  4.+0.j]

In [14]:
antenna.plot_s_db(m=0, n=0)
antenna.plot_s_db(m=1, n=1)
legend(loc='best'); grid(True)
title('random capacitor values')
ylim([-20,0])


Out[14]:
(-20, 0)

Matching Calculation

In this section we define the matching strategy to match the antenna at a given frequency, that is, to find the set of four capacitors values for which the (active) return loss is minimized for a given phase excitation (monopole, dipole, etc).

First, let's defined the parameters we will look for:


In [15]:
def get_s_active(S, a_in):
    '''
    Returns the "active" S-parameters, defined by 
    
    S1 = S11*a1/a1 + S12*a2/a1
    S2 = S21*a1/a2 + S22*a2/a2
    '''
    S1_active = (S[:,0,0]*a_in[0] + S[:,0,1]*a_in[1])/a_in[0]
    S2_active = (S[:,1,0]*a_in[0] + S[:,1,1]*a_in[1])/a_in[1]    
    # transpose in order to have an array f x 2
    return(np.transpose([S1_active, S2_active]))

def get_z_active(S, a_in, z0):
    '''
    Returns the "active" Z-parameters, defined by
    
    Zi = Z0i*(1+Sact_i)/(1-Sact_i)
    '''
    Sact = get_s_active(S, a_in)
    # active impedance parameters
    Z1_active = z0[:,0]*(1+Sact[:,0])/(1-Sact[:,0])
    Z2_active = z0[:,1]*(1+Sact[:,1])/(1-Sact[:,1])
    # transpose in order to have an array f x 2
    return(np.transpose([Z1_active, Z2_active]))

def get_vswr_active(Sact):
    '''
    Returns the "active" VSWR, defined from the "active" S-parameters 
    
    VSWRact = 1+|Sact| / 1-|Sact|
    '''
    vswr1_active = (1+np.abs(Sact[:,0]))/(1-np.abs(Sact[:,0]))
    vswr2_active = (1+np.abs(Sact[:,1]))/(1-np.abs(Sact[:,1]))
    # transpose in order to have an array f x 2
    return(np.transpose([vswr1_active, vswr2_active]))

Below we define the minimization criteria that the optimization routine will use:


In [16]:
def optim_fun(C, f_match, freq, a_in, bridge, front_face):
    '''
    Optimization function
    
    returns
        y: minimization criteria
    '''
    antenna = antenna_network(C, freq, bridge, front_face)
    # frequency index for matching frequency
    index_f_match = np.argmin(np.abs(freq.f - f_match))
    
    S_active = get_s_active(antenna.s, a_in)    
    # target : minimize return loss
    y = [np.real(S_active[index_f_match,0]) - 0,
         np.real(S_active[index_f_match,1]) - 0,
         np.imag(S_active[index_f_match,0]) - 0,
         np.imag(S_active[index_f_match,1]) - 0]
    
    # other possible target : minimize as-seen impedance
    #Z_active = get_z_active(antenna.s, a_in, antenna.z0)    
    #y = [np.real(Z_active[index_f_match,0]) - T_Z0,
    #     np.imag(Z_active[index_f_match,0]) - 0,
    #     np.real(Z_active[index_f_match,1]) - T_Z0,
    #     np.imag(Z_active[index_f_match,1]) - 0]     
    
    
    return(y)

Matching Example

We are looking for a match @


In [17]:
# matching frequency
f_match = 35e6

# excitation mode
a_in = [+1, -1]

Below is the call to the optimization routine. As the results heavily depends of the starting point location, we first generates a set of initial guess for the capacitor set, and loop until we find a set of values which is physical, ie which is within the capacitor range.


In [18]:
import scipy.optimize
success = False
while success == False:
    # generates a random capacitors set between 50pF and 300pF
    C0 = 50e-12 + scipy.random.rand(4)*(300e-12 - 50e-12)
    # scipy.optimize.root : Unified interface for nonlinear solvers of multivariate functions
    # 'lm' stands for least square sense. This methods tends to obtain the first solution in few loops
    sol = scipy.optimize.root(optim_fun, C0, args=(f_match, frequency, a_in, bridge, front_face), method='lm')
    success = sol.success
            
    print(success, sol.x/1e-12)
                
    for idm,Cm in enumerate(sol.x):
        if (Cm < 50e-12) or (Cm > 300e-12):
            success = False
            print('Solution found out of capacitor range ! Re-doing...')
    
    print(sol.x/1e-12)


(True, array([  1.19999508e+02,   1.13988563e+02,   8.90613893e+05,
         1.23340454e+02]))
Solution found out of capacitor range ! Re-doing...
[  1.19999508e+02   1.13988563e+02   8.90613893e+05   1.23340454e+02]
(True, array([  1.23265724e+02,   2.57193325e+06,   1.14076572e+02,
         1.20068285e+02]))
Solution found out of capacitor range ! Re-doing...
[  1.23265724e+02   2.57193325e+06   1.14076572e+02   1.20068285e+02]
(True, array([  1.15775175e+02,  -6.96247170e+11,  -1.00575020e+12,
         1.15799706e+02]))
Solution found out of capacitor range ! Re-doing...
Solution found out of capacitor range ! Re-doing...
[  1.15775175e+02  -6.96247170e+11  -1.00575020e+12   1.15799706e+02]
(True, array([  3.02227367e+11,   3.05024507e+11,   1.16873158e+02,
        -1.01967625e+12]))
Solution found out of capacitor range ! Re-doing...
Solution found out of capacitor range ! Re-doing...
Solution found out of capacitor range ! Re-doing...
[  3.02227367e+11   3.05024507e+11   1.16873158e+02  -1.01967625e+12]
(True, array([ 116.83038838,  116.98747154,  125.14041303,  125.52441614]))
[ 116.83038838  116.98747154  125.14041303  125.52441614]

In [19]:
# Other possible solution sets: 

# [ 126.05220189  108.21202537  108.28658675  126.13734406]
# [ 108.35017469  125.98770253  126.09622241  108.38332422]
# [ 115.98029063  116.08399439  126.19607927  126.46226211]

# [ 124.09846906  109.49529582  109.56485206  124.18099362]
# [ 109.65263647  124.04204356  124.15023392  109.68018413]
# [ 116.83038838  116.98747154  125.14041303  125.52441614]
# [ 125.44505279  125.05855338  117.06790523  116.88759117]
print("Match point : {C} pF".format(C=sol.x/1e-12))


Match point : [ 116.83038838  116.98747154  125.14041303  125.52441614] pF

Assuming that the match point has been found, we put this solution into the antenna:


In [20]:
C=sol.x
antenna = antenna_network(C, frequency, bridge, front_face)

In [21]:
antenna.plot_s_db(m=0, n=0)
antenna.plot_s_db(m=1, n=1)
grid(True)



In [22]:
S_active = get_s_active(antenna.s, a_in)
plot(frequency.f/1e6, 20*np.log10(np.abs(S_active)))
grid(True)
title('"Active" S-parameters')
ylabel('Magnitude [dB]')
xlabel('f [MHz]')


Out[22]:
<matplotlib.text.Text at 0x1450fd30>

In [22]: