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
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]:
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]:
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))
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]:
In [8]:
# Passivity check
np.allclose(np.eye(3), np.dot(S_bridge_ideal, np.transpose(np.conjugate(S_bridge_ideal))))
Out[8]:
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]:
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]:
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
Out[13]:
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]:
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)
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)
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))
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]:
In [22]: