2016-11-15 19:08:41 +00:00
|
|
|
# -*- coding: utf-8 -*-
|
|
|
|
"""
|
|
|
|
Tutorials / CRLH_Extraction
|
|
|
|
|
2022-03-11 21:17:27 +00:00
|
|
|
Description at:
|
2016-11-15 19:08:41 +00:00
|
|
|
http://openems.de/index.php/Tutorial:_CRLH_Extraction
|
|
|
|
|
|
|
|
Tested with
|
|
|
|
- python 3.4
|
|
|
|
- openEMS v0.0.34+
|
|
|
|
|
|
|
|
(C) 2016 Thorsten Liebig <thorsten.liebig@gmx.de>
|
|
|
|
"""
|
|
|
|
|
|
|
|
|
|
|
|
### Import Libraries
|
|
|
|
import os, tempfile
|
|
|
|
from pylab import *
|
|
|
|
|
|
|
|
from CSXCAD import ContinuousStructure
|
|
|
|
from openEMS import openEMS
|
|
|
|
from openEMS.physical_constants import *
|
|
|
|
|
|
|
|
### Class to represent single CRLH unit cells
|
|
|
|
class CRLH_Cells:
|
|
|
|
def __init__(self, LL, LW, Top, Bot, GLT, GLB, SL, SW, VR):
|
|
|
|
self.LL = LL # Line length
|
|
|
|
self.LW = LW # Line width
|
|
|
|
self.Top = Top # top signal height
|
|
|
|
self.Bot = Bot # bottom signal height
|
|
|
|
self.GLT = GLT # gap length top
|
|
|
|
self.GLB = GLB # gap length bottom
|
|
|
|
self.SL = SL # stub length
|
|
|
|
self.SW = SW # stub width
|
|
|
|
self.VR = VR # via radius
|
|
|
|
self.props = dict() # property dictionary
|
|
|
|
self.edge_resolution = None
|
|
|
|
|
|
|
|
def createProperties(self, CSX):
|
|
|
|
for p in ['metal_top', 'metal_bot', 'via']:
|
|
|
|
self.props[p] = CSX.AddMetal(p)
|
|
|
|
|
|
|
|
def setEdgeResolution(self, res):
|
|
|
|
self.edge_resolution = res
|
|
|
|
|
|
|
|
def createCell(self, translate = [0,0,0]):
|
|
|
|
def append_mesh(mesh1, mesh2):
|
|
|
|
for n in range(3):
|
|
|
|
if mesh1[n] is None:
|
|
|
|
mesh1[n] = mesh2[n]
|
|
|
|
elif mesh2[n] is None:
|
|
|
|
continue
|
|
|
|
else:
|
|
|
|
mesh1[n] += mesh2[n]
|
|
|
|
return mesh1
|
|
|
|
translate = array(translate)
|
|
|
|
start = [-self.LL/2 , -self.LW/2, self.Top] + translate
|
|
|
|
stop = [-self.GLT/2, self.LW/2, self.Top] + translate
|
|
|
|
box = self.props['metal_top'].AddBox(start, stop, priority=10)
|
|
|
|
mesh = box.GetGridHint('x', metal_edge_res=self.edge_resolution, down_dir=False)
|
|
|
|
append_mesh(mesh, box.GetGridHint('y', metal_edge_res=self.edge_resolution) )
|
|
|
|
|
|
|
|
start = [+self.LL/2 , -self.LW/2, self.Top] + translate
|
|
|
|
stop = [+self.GLT/2, self.LW/2, self.Top] + translate
|
|
|
|
box = self.props['metal_top'].AddBox(start, stop, priority=10)
|
|
|
|
append_mesh(mesh, box.GetGridHint('x', metal_edge_res=self.edge_resolution, up_dir=False) )
|
|
|
|
|
|
|
|
start = [-(self.LL-self.GLB)/2, -self.LW/2, self.Bot] + translate
|
|
|
|
stop = [+(self.LL-self.GLB)/2, self.LW/2, self.Bot] + translate
|
|
|
|
box = self.props['metal_bot'].AddBox(start, stop, priority=10)
|
|
|
|
append_mesh(mesh, box.GetGridHint('x', metal_edge_res=self.edge_resolution) )
|
|
|
|
|
|
|
|
start = [-self.SW/2, -self.LW/2-self.SL, self.Bot] + translate
|
|
|
|
stop = [+self.SW/2, self.LW/2+self.SL, self.Bot] + translate
|
|
|
|
box = self.props['metal_bot'].AddBox(start, stop, priority=10)
|
|
|
|
append_mesh(mesh, box.GetGridHint('xy', metal_edge_res=self.edge_resolution) )
|
|
|
|
|
|
|
|
start = [0, -self.LW/2-self.SL+self.SW/2, 0 ] + translate
|
|
|
|
stop = [0, -self.LW/2-self.SL+self.SW/2, self.Bot] + translate
|
|
|
|
|
|
|
|
self.props['via'].AddCylinder(start, stop, radius=self.VR, priority=10)
|
|
|
|
|
|
|
|
start[1] *= -1
|
|
|
|
stop [1] *= -1
|
|
|
|
self.props['via'].AddCylinder(start, stop, radius=self.VR, priority=10)
|
|
|
|
|
|
|
|
return mesh
|
|
|
|
|
|
|
|
|
|
|
|
if __name__ == '__main__':
|
|
|
|
### Setup the simulation
|
|
|
|
Sim_Path = os.path.join(tempfile.gettempdir(), 'CRLH_Extraction')
|
|
|
|
post_proc_only = False
|
|
|
|
|
|
|
|
unit = 1e-6 # specify everything in um
|
|
|
|
|
|
|
|
feed_length = 30000
|
|
|
|
|
|
|
|
substrate_thickness = [1524, 101 , 254 ]
|
|
|
|
substrate_epsr = [3.48, 3.48, 3.48]
|
|
|
|
|
|
|
|
CRLH = CRLH_Cells(LL = 14e3, LW = 4e3, GLB = 1950, GLT = 4700, SL = 7800, SW = 1000, VR = 250 , \
|
|
|
|
Top = sum(substrate_thickness), \
|
|
|
|
Bot = sum(substrate_thickness[:-1]))
|
|
|
|
|
|
|
|
# frequency range of interest
|
|
|
|
f_start = 0.8e9
|
|
|
|
f_stop = 6e9
|
|
|
|
|
|
|
|
### Setup FDTD parameters & excitation function
|
|
|
|
CSX = ContinuousStructure()
|
|
|
|
FDTD = openEMS(EndCriteria=1e-5)
|
|
|
|
FDTD.SetCSX(CSX)
|
|
|
|
mesh = CSX.GetGrid()
|
|
|
|
mesh.SetDeltaUnit(unit)
|
|
|
|
|
|
|
|
CRLH.createProperties(CSX)
|
|
|
|
|
|
|
|
FDTD.SetGaussExcite((f_start+f_stop)/2, (f_stop-f_start)/2 )
|
|
|
|
BC = {'PML_8' 'PML_8' 'MUR' 'MUR' 'PEC' 'PML_8'}
|
|
|
|
FDTD.SetBoundaryCond( ['PML_8', 'PML_8', 'MUR', 'MUR', 'PEC', 'PML_8'] )
|
|
|
|
|
|
|
|
### Setup a basic mesh and create the CRLH unit cell
|
|
|
|
resolution = C0/(f_stop*sqrt(max(substrate_epsr)))/unit /30 # resolution of lambda/30
|
|
|
|
CRLH.setEdgeResolution(resolution/4)
|
|
|
|
|
|
|
|
mesh.SetLines('x', [-feed_length-CRLH.LL/2, 0, feed_length+CRLH.LL/2])
|
|
|
|
mesh.SetLines('y', [-30000, 0, 30000])
|
|
|
|
|
|
|
|
substratelines = cumsum(substrate_thickness)
|
|
|
|
mesh.SetLines('z', [0, 20000])
|
|
|
|
mesh.AddLine('z', cumsum(substrate_thickness))
|
|
|
|
mesh.AddLine('z', linspace(substratelines[-2],substratelines[-1],4))
|
|
|
|
|
|
|
|
# create the CRLH unit cell (will define additional fixed mesh lines)
|
|
|
|
mesh_hint = CRLH.createCell()
|
|
|
|
mesh.AddLine('x', mesh_hint[0])
|
|
|
|
mesh.AddLine('y', mesh_hint[1])
|
|
|
|
|
|
|
|
# Smooth the given mesh
|
|
|
|
mesh.SmoothMeshLines('all', resolution, 1.2)
|
|
|
|
|
|
|
|
### Setup the substrate layer
|
|
|
|
substratelines = [0] + substratelines.tolist()
|
|
|
|
start, stop = mesh.GetSimArea()
|
|
|
|
|
|
|
|
for n in range(len(substrate_thickness)):
|
|
|
|
sub = CSX.AddMaterial( 'substrate_{}'.format(n), epsilon=substrate_epsr[n] )
|
|
|
|
start[2] = substratelines[n]
|
|
|
|
stop [2] = substratelines[n+1]
|
|
|
|
|
|
|
|
sub.AddBox( start, stop )
|
|
|
|
|
|
|
|
### Add the feeding MSL ports
|
|
|
|
pec = CSX.AddMetal( 'PEC' )
|
|
|
|
port = [None, None]
|
|
|
|
x_lines = mesh.GetLines('x')
|
|
|
|
portstart = [ x_lines[0], -CRLH.LW/2, substratelines[-1]]
|
|
|
|
portstop = [ -CRLH.LL/2, CRLH.LW/2, 0]
|
|
|
|
port[0] = FDTD.AddMSLPort( 1, pec, portstart, portstop, 'x', 'z', excite=-1, FeedShift=10*resolution, MeasPlaneShift=feed_length/2, priority=10)
|
|
|
|
|
|
|
|
|
|
|
|
portstart = [ x_lines[-1], -CRLH.LW/2, substratelines[-1]]
|
|
|
|
portstop = [ +CRLH.LL/2 , CRLH.LW/2, 0]
|
|
|
|
port[1] = FDTD.AddMSLPort( 2, pec, portstart, portstop, 'x', 'z', MeasPlaneShift=feed_length/2, priority=10)
|
|
|
|
|
|
|
|
### Run the simulation
|
|
|
|
if 1: # debugging only
|
|
|
|
CSX_file = os.path.join(Sim_Path, 'CRLH_Extraction.xml')
|
|
|
|
if not os.path.exists(Sim_Path):
|
|
|
|
os.mkdir(Sim_Path)
|
|
|
|
CSX.Write2XML(CSX_file)
|
|
|
|
os.system(r'AppCSXCAD "{}"'.format(CSX_file))
|
|
|
|
|
|
|
|
if not post_proc_only:
|
|
|
|
FDTD.Run(Sim_Path, verbose=3, cleanup=True)
|
|
|
|
|
|
|
|
### Post-Processing
|
|
|
|
f = linspace( f_start, f_stop, 1601 )
|
|
|
|
for p in port:
|
|
|
|
p.CalcPort( Sim_Path, f, ref_impedance = 50, ref_plane_shift = feed_length)
|
|
|
|
|
|
|
|
# calculate and plot scattering parameter
|
|
|
|
s11 = port[0].uf_ref / port[0].uf_inc
|
|
|
|
s21 = port[1].uf_ref / port[0].uf_inc
|
|
|
|
|
|
|
|
plot(f/1e9,20*log10(abs(s11)),'k-' , linewidth=2, label='$S_{11}$')
|
|
|
|
plot(f/1e9,20*log10(abs(s21)),'r--', linewidth=2, label='$S_{21}$')
|
|
|
|
grid()
|
|
|
|
legend(loc=3)
|
|
|
|
ylabel('S-Parameter (dB)')
|
|
|
|
xlabel('frequency (GHz)')
|
|
|
|
ylim([-40, 2])
|
|
|
|
|
|
|
|
### Extract CRLH parameter form ABCD matrix
|
|
|
|
A = ((1+s11)*(1-s11) + s21*s21)/(2*s21)
|
|
|
|
C = ((1-s11)*(1-s11) - s21*s21)/(2*s21) / port[1].Z_ref
|
|
|
|
|
|
|
|
Y = C
|
|
|
|
Z = 2*(A-1)/C
|
|
|
|
|
|
|
|
iZ = imag(Z)
|
|
|
|
iY = imag(Y)
|
|
|
|
|
|
|
|
fse = interp(0, iZ, f)
|
|
|
|
fsh = interp(0, iY, f)
|
|
|
|
|
|
|
|
df = f[1]-f[0]
|
|
|
|
fse_idx = np.where(f>fse)[0][0]
|
|
|
|
fsh_idx = np.where(f>fsh)[0][0]
|
|
|
|
|
|
|
|
LR = 0.5*(iZ[fse_idx]-iZ[fse_idx-1])/(2*pi*df)
|
|
|
|
CL = 1/(2*pi*fse)**2/LR
|
|
|
|
|
|
|
|
CR = 0.5*(iY[fsh_idx]-iY[fsh_idx-1])/(2*pi*df)
|
|
|
|
LL = 1/(2*pi*fsh)**2/CR
|
|
|
|
|
|
|
|
print(' Series tank: CL = {:.2f} pF, LR = {:.2f} nH -> f_se = {:.2f} GHz '.format(CL*1e12, LR*1e9, fse*1e-9))
|
|
|
|
print(' Shunt tank: CR = {:.2f} pF, LL = {:.2f} nH -> f_sh = {:.2f} GHz '.format(CR*1e12, LL*1e9, fsh*1e-9))
|
|
|
|
|
|
|
|
### Calculate analytical wave-number of an inf-array of cells
|
|
|
|
w = 2*pi*f
|
|
|
|
wse = 2*pi*fse
|
|
|
|
wsh = 2*pi*fsh
|
|
|
|
beta_calc = real(arccos(1-(w**2-wse**2)*(w**2-wsh**2)/(2*w**2/CR/LR)))
|
|
|
|
|
|
|
|
# plot
|
|
|
|
figure()
|
|
|
|
beta = -angle(s21)/CRLH.LL/unit
|
|
|
|
plot(abs(beta)*CRLH.LL*unit/pi,f*1e-9,'k-', linewidth=2, label=r'$\beta_{CRLH,\ 1\ cell}$' )
|
|
|
|
grid()
|
|
|
|
plot(beta_calc/pi,f*1e-9,'c--', linewidth=2, label=r'$\beta_{CRLH,\ \infty\ cells}$')
|
|
|
|
plot(real(port[1].beta)*CRLH.LL*unit/pi,f*1e-9,'g-', linewidth=2, label=r'$\beta_{MSL}$')
|
|
|
|
ylim([1, 6])
|
|
|
|
xlabel(r'$|\beta| p / \pi$')
|
|
|
|
ylabel('frequency (GHz)')
|
|
|
|
legend(loc=2)
|
|
|
|
|
|
|
|
show()
|