1238 lines
59 KiB
Python
1238 lines
59 KiB
Python
|
||
import nazca as nd
|
||
import numpy as np
|
||
from numpy import pi
|
||
|
||
from ....routing import Route
|
||
from ....structures import _my_polygon,circle,Clothoid
|
||
from ....basic import __cell_arg__
|
||
|
||
class ring_bus_wg:
|
||
## two types:
|
||
## DC, BDC
|
||
"""
|
||
Initialize ring-bus waveguide coupler settings.
|
||
|
||
Parameters
|
||
----------
|
||
xs : str, optional
|
||
Waveguide cross-section name (default is "strip").
|
||
R_cp : int, optional
|
||
Coupling waveguide bend radius in microns for BDC mode (default is 20).
|
||
w_bus : float, optional
|
||
Coupling waveguide width in microns (default is 0.5).
|
||
w_wg : float, optional
|
||
Port waveguide width in microns (default is 0.5).
|
||
bend_DC : bool, optional
|
||
Use bend directional coupler (True) or straight DC (False, default is True).
|
||
dLc : int, optional
|
||
Straight coupling length in microns for DC mode (default is 10).
|
||
dAc : int, optional
|
||
Coupling angle in degrees for BDC mode (default is 10).
|
||
euler_transistion : bool, optional
|
||
Enable Euler transition segments before/after the coupling arc (default is False).
|
||
dL_trans : int, optional
|
||
Straight transition length in microns when Euler transition is enabled (default is 10).
|
||
dA_trans : int, optional
|
||
Transition bend angle in degrees for the Euler segment (default is 30).
|
||
R_max_trans : int, optional
|
||
Maximum radius in microns for the transition segment (default is 100).
|
||
w_trans : float, optional
|
||
Waveguide width in microns inside the transition (default is 0.5).
|
||
euler_anti_bend : bool, optional
|
||
Enable Euler anti-bend routing after the coupling section (default is False).
|
||
R_max_anti : int, optional
|
||
Maximum radius in microns for the anti-bend segment (default is 100).
|
||
R_min_anti : int, optional
|
||
Minimum radius in microns for the anti-bend segment (default is 10).
|
||
A_anti : float, optional
|
||
Anti-bend angle in degrees (default is None, meaning auto-calculated).
|
||
res : float, optional
|
||
Geometry discretization step in microns (default is 0.1).
|
||
wg_Ltp : int, optional
|
||
Port taper length in microns (default is 5).
|
||
dL_p2p : float, optional
|
||
Target horizontal spacing in microns between input/output ports (default is None).
|
||
sharp_patch : bool, optional
|
||
Insert chamfer polygons to avoid sharp corners (default is True).
|
||
show_pins : bool, optional
|
||
Draw Nazca stub markers for debugging (default is False).
|
||
end_patch : bool, optional
|
||
Force small straight fillers at the end of Euler segments (default is False).
|
||
clothoid_order : int, optional
|
||
Order of the spiral section used inside :class:`Clothoid` transitions (default is 1).
|
||
"""
|
||
|
||
def __init__(self,
|
||
xs='strip',
|
||
R_cp = 20,
|
||
w_bus = 0.5,
|
||
bend_DC = True,
|
||
w_wg = 0.5,
|
||
dLc = 10,
|
||
dAc = 10,
|
||
|
||
euler_transistion = False,
|
||
dL_trans = 10,
|
||
dA_trans = 30,
|
||
R_max_trans = 100,
|
||
w_trans = 0.5,
|
||
|
||
euler_anti_bend = False,
|
||
R_max_anti = 100,
|
||
R_min_anti = 10,
|
||
A_anti = None,
|
||
res = 0.1,
|
||
wg_Ltp = 5,
|
||
dL_p2p = None,
|
||
sharp_patch = True,
|
||
show_pins = False,
|
||
end_patch = False,
|
||
clothoid_order = 1,
|
||
) -> None:
|
||
|
||
self.xs = xs
|
||
self.R_cp = R_cp
|
||
self.w_bus = w_bus
|
||
self.dLc = dLc
|
||
self.dAc = dAc
|
||
# self.n_points = n_points
|
||
self.w_wg = w_wg
|
||
self.bend_DC = bend_DC
|
||
self.euler_transistion = euler_transistion
|
||
self.dL_trans = dL_trans
|
||
self.dA_trans = dA_trans
|
||
self.R_max_trans = R_max_trans
|
||
self.w_trans = w_trans
|
||
# self.euler_anti_bend = euler_anti_bend ## parameter abondond
|
||
self.R_max_anti = R_max_anti
|
||
self.R_min_anti = R_min_anti
|
||
self.A_anti = A_anti
|
||
self.wg_Ltp = wg_Ltp
|
||
self.dL_p2p = dL_p2p
|
||
self.res = res
|
||
|
||
self.end_patch = end_patch
|
||
|
||
self.L = 0
|
||
self.clothoid_order = clothoid_order
|
||
self.cell = self.generate_gds(sharp_patch=sharp_patch,show_pins=show_pins)
|
||
|
||
def generate_gds(self,sharp_patch,show_pins=False):
|
||
with nd.Cell(instantiate=False) as C:
|
||
|
||
w_crack = 0.002
|
||
if (self.bend_DC and self.euler_transistion):
|
||
if (self.A_anti == None):
|
||
self.A_anti = self.dAc/2+self.dA_trans
|
||
|
||
cp = Clothoid(R=[self.R_cp,self.R_cp,self.R_max_trans,self.R_min_anti,self.R_max_anti],
|
||
A=[0, self.dAc/2,self.dAc/2+self.dA_trans,(self.dAc/2+self.dA_trans) - self.A_anti/2,(self.dAc/2+self.dA_trans) - self.A_anti],
|
||
w=[self.w_bus,self.w_bus,self.w_trans,(self.w_trans+self.w_wg)/2,self.w_wg],xs=self.xs,
|
||
spiral_order=[1,self.clothoid_order,1,1],
|
||
sharp_patch=sharp_patch,end_patch=self.end_patch)
|
||
ar = cp.cell.put('a1',0,0,0).pin['b1']
|
||
al = cp.cell.put('a1',0,0,180,flip=1).pin['b1']
|
||
|
||
nd.strt(length=w_crack,width=self.w_bus,xs=self.xs).put(-w_crack/2,0,0)
|
||
nd.strt(length=w_crack,width=self.w_wg,xs=self.xs).put(ar.x-w_crack/2,ar.y,0)
|
||
nd.strt(length=w_crack,width=self.w_wg,xs=self.xs).put(al.x-w_crack/2,al.y,0)
|
||
|
||
self.L = self.L + cp.L0
|
||
|
||
elif (self.bend_DC):
|
||
""" Bend DC without Euler transision """
|
||
|
||
# if (self.bend_DC):
|
||
cp = circle(xs=self.xs,radius=self.R_cp, width = self.w_bus, theta_start = 270-self.dAc/2, theta_stop=270+self.dAc/2,res=self.res,
|
||
# n_points=self.n_points,
|
||
sharp_patch=sharp_patch).cell.put(0,self.R_cp,0)
|
||
al = cp.pin['a1']
|
||
ar = cp.pin['b1']
|
||
|
||
self.L = self.L + self.R_cp*self.dAc/180*np.pi
|
||
|
||
self.w_trans = self.w_bus
|
||
TL = nd.strt(length=self.dL_trans,width=self.w_bus,xs=self.xs).put('a0',al)
|
||
TR = nd.strt(length=self.dL_trans,width=self.w_bus,xs=self.xs).put('a0',ar)
|
||
Ainner = self.dAc/2
|
||
|
||
self.L = self.L + self.dL_trans*2
|
||
|
||
if (self.A_anti == None):
|
||
self.A_anti = Ainner
|
||
|
||
Anti = circle(xs=self.xs,radius=self.R_max_anti, width = self.w_trans, theta_start = 90, theta_stop=90+self.A_anti,res=self.res,
|
||
# n_points=self.n_points,
|
||
sharp_patch=sharp_patch)
|
||
ar = Anti.cell.put('b1',TR.pin['b0']).pin['a1']
|
||
al = Anti.cell.put('b1',TL.pin['b0'],flip=1).pin['a1']
|
||
|
||
self.L = self.L + self.R_max_anti*self.A_anti*2*180/np.pi
|
||
|
||
|
||
TPR = nd.taper(length=self.wg_Ltp,width1=ar.width,width2=self.w_wg,xs=self.xs).put('a0',ar)
|
||
TPL = nd.taper(length=self.wg_Ltp,width1=al.width,width2=self.w_wg,xs=self.xs).put('a0',al)
|
||
self.L = self.L + self.wg_Ltp*2
|
||
|
||
L = (TPR.pin['b0'].x - TPL.pin['b0'].x) ## L distance pin2pin
|
||
if (self.dL_p2p!=None):
|
||
if (L<self.dL_p2p):
|
||
dL_patch = (self.dL_p2p - L)/2/np.cos(TPR.pin['b0'].a/180*np.pi)
|
||
TPR = nd.strt(length=dL_patch,width=self.w_wg,xs=self.xs).put(TPR.pin['b0'])
|
||
TPL = nd.strt(length=dL_patch,width=self.w_wg,xs=self.xs).put(TPL.pin['b0'])
|
||
|
||
self.L = self.L + dL_patch*2
|
||
|
||
# self.dL_patch = dL_patch
|
||
nd.Pin(name='a1',pin=TPL.pin['b0'],width=self.w_wg).put()
|
||
nd.Pin(name='b1',pin=TPR.pin['b0'],width=self.w_wg).put()
|
||
|
||
nd.strt(length=0.1,width=TPL.pin['b0'].width,xs=self.xs).put(TPL.pin['b0'].move(-0.05,0,0))
|
||
nd.strt(length=0.1,width=TPR.pin['b0'].width,xs=self.xs).put(TPR.pin['b0'].move(-0.05,0,0))
|
||
|
||
self.w = self.w_bus
|
||
self.sz = [abs(TPL.pin['b0'].x-TPR.pin['b0'].x),abs(TPL.pin['b0'].y)]
|
||
|
||
if (show_pins):
|
||
nd.put_stub()
|
||
|
||
return C
|
||
|
||
|
||
class ADC_STD_2x2:
|
||
"""
|
||
General-purpose 2×2 adiabatic directional coupler scaffold.
|
||
|
||
Parameters
|
||
----------
|
||
name : str, optional
|
||
Unique cell identifier (default is None, meaning no instantiation).
|
||
xs : str, optional
|
||
Registered Nazca cross-section name (default is "strip").
|
||
wu0 : float, optional
|
||
Upper waveguide width at the input plane in microns (default is 0.45).
|
||
wu1 : float, optional
|
||
Upper waveguide width at the output plane in microns (default is 0.61).
|
||
wu_in : float, optional
|
||
Upper input port width in microns (default is 0.45).
|
||
wu_out : float, optional
|
||
Upper output port width in microns (default is 0.8).
|
||
wd0 : float, optional
|
||
Lower waveguide width at the input plane in microns (default is 0.33).
|
||
wd1 : float, optional
|
||
Lower waveguide width at the output plane in microns (default is 0.2).
|
||
wd_in : float, optional
|
||
Lower input port width in microns (default is 0.45).
|
||
wd_out : float, optional
|
||
Lower output port width in microns (default is 0.8).
|
||
Lu : int, optional
|
||
Interaction length of the upper core in microns (default is 33).
|
||
Ld : int, optional
|
||
Interaction length of the lower core in microns (default is 33).
|
||
angle : int, optional
|
||
Sbend deflection angle in degrees (default is 20).
|
||
g0 : float, optional
|
||
Gap between the two cores at the input plane in microns (default is 0.2).
|
||
g1 : float, optional
|
||
Gap between the two cores at the output plane in microns (default is 0.2).
|
||
sbend_type : str, optional
|
||
Type of IO transition ("euler" or "circular", default is "euler").
|
||
Rmax : optional
|
||
Maximum Euler bend radius in microns (default is None, meaning auto).
|
||
Rmin : int, optional
|
||
Minimum Euler bend radius in microns (default is 5).
|
||
Ru0 : int, optional
|
||
Upper input bend radius in microns (default is 0).
|
||
Ru1 : int, optional
|
||
Upper output bend radius in microns (default is 20).
|
||
Rd0 : int, optional
|
||
Lower input bend radius in microns (default is 20).
|
||
Rd1 : int, optional
|
||
Lower output bend radius in microns (default is 0).
|
||
tp_angle : int, optional
|
||
Half-angle of straight tapers in degrees when Euler bends are disabled (default is 2).
|
||
sharp_patch : bool, optional
|
||
Insert chamfer polygons to reduce acute corners (default is True).
|
||
show_pins : bool, optional
|
||
Draw Nazca stub markers for debugging (default is False).
|
||
euler_points : int, optional
|
||
Number of sampling points for Euler/Clothoid evaluation (default is 64).
|
||
res : float, optional
|
||
Geometry discretization step in microns (default is 0.1).
|
||
"""
|
||
|
||
def __init__ (self,
|
||
name = None,
|
||
xs='strip',
|
||
wu0=0.45,
|
||
wu1=0.61,
|
||
wu_in=0.45,
|
||
wu_out=0.8,
|
||
wd0=0.33,
|
||
wd1=0.20,
|
||
wd_in=0.45,
|
||
wd_out=0.8,
|
||
Lu=33,
|
||
Ld=33,
|
||
angle=20,
|
||
g0=0.2,
|
||
g1=0.2,
|
||
sbend_type='euler',
|
||
Rmax=None,
|
||
Rmin=5,
|
||
Ru0=0,
|
||
Ru1=20,
|
||
Rd0=20,
|
||
Rd1=0,
|
||
tp_angle=2,
|
||
sharp_patch=True,
|
||
show_pins=False,
|
||
euler_points = 64,
|
||
res = 0.1):
|
||
|
||
self.name = name
|
||
if (self.name==None):
|
||
self.instantiate = False
|
||
else :
|
||
self.instantiate = True
|
||
|
||
self.wu0 = wu0
|
||
self.xs = xs
|
||
self.wu1 = wu1
|
||
self.wu_in = wu_in
|
||
self.wu_out = wu_out
|
||
self.wd0 = wd0
|
||
self.wd1 = wd1
|
||
self.wd_in = wd_in
|
||
self.wd_out = wd_out
|
||
self.Lu = Lu
|
||
self.Ld = Ld
|
||
self.angle = angle
|
||
self.g0 = g0
|
||
self.g1 = g1
|
||
self.sbend_type = sbend_type
|
||
self.Rmax = Rmax
|
||
self.Rmin = Rmin
|
||
self.sharp_patch = sharp_patch
|
||
self.euler_points = euler_points
|
||
self.res = res
|
||
|
||
if (Rmax!=None and Rmax!=0):
|
||
if (Ru0!=0):
|
||
Ru0 = Rmax
|
||
if (Ru1!=0):
|
||
Ru1 = Rmax
|
||
if (Rd0!=0):
|
||
Rd0 = Rmax
|
||
if (Rd1!=0):
|
||
Rd1 = Rmax
|
||
|
||
self.Ru0 = Ru0
|
||
self.Ru1 = Ru1
|
||
self.Rd0 = Rd0
|
||
self.Rd1 = Rd1
|
||
self.tp_angle = tp_angle
|
||
self.sharp_patch = sharp_patch
|
||
self.cell = self.generate_gds(err=0,show_pins=show_pins)
|
||
self.L = np.abs(self.cell.pin['a1'].x - self.cell.pin['b1'].x)
|
||
|
||
def generate_gds(self,err=0,show_pins=False):
|
||
|
||
with nd.Cell(instantiate = self.instantiate, name=self.name ) as C:
|
||
for layers,growx,growy,acc in nd.layeriter(xs=self.xs):
|
||
(a1,b1), (a2,b2),c1,c2 = growx
|
||
|
||
vtx_upper_x = np.array([0,self.Lu,self.Lu,0])
|
||
vtx_lower_x = np.array([0,self.Ld,self.Ld,0])
|
||
|
||
## consisting the error
|
||
vtx_upper_y = np.array([ (self.g0-err)/2+b2, (self.g1-err)/2+b2, (self.g1-err)/2+(self.wu1+err)+b1, (self.g0-err)/2+(self.wu0+err)+b1])
|
||
vtx_lower_y = np.array([-(self.g0-err)/2+b1,-(self.g1-err)/2+b1,-(self.g1-err)/2-(self.wd1+err)+b2,-(self.g0-err)/2-(self.wd0+err)+b2])
|
||
vtx_upper_x = np.array([0,self.Lu,self.Lu,0])
|
||
vtx_lower_x = np.array([0,self.Ld,self.Ld,0])
|
||
|
||
vtx_upper = np.c_[vtx_upper_x,vtx_upper_y]
|
||
vtx_lower = np.c_[vtx_lower_x,vtx_lower_y]
|
||
|
||
_my_polygon(layers,vtx_upper).put(0,0,0)
|
||
_my_polygon(layers,vtx_lower).put(0,0,0)
|
||
|
||
""" input waveguide wires of the left, upper port """
|
||
if (self.Rd0!=0):
|
||
## placing the adiabatic bend attachment to lower waveguide, input port, label 0
|
||
if (self.sbend_type=='euler'):
|
||
|
||
attach_in = Clothoid(R=[self.Rd0,self.Rmin,self.Rd0] , w=[self.wd0,self.wd0], A=[0,self.angle/2,self.angle], dL_cal=0.001,dL_wg=self.res,
|
||
xs=self.xs,width_type='linear',
|
||
sharp_patch=self.sharp_patch,spiral_order=1)
|
||
attach_in.cell.put(0,vtx_lower_y[0]/2+vtx_lower_y[-1]/2,180,flip=0)
|
||
pin_a2 = Clothoid(R=[self.Rd0,self.Rmin,self.Rd0] , w=[self.wd0,self.wd_in], A=[self.angle,self.angle/2,0], dL_cal=0.001,dL_wg=self.res,
|
||
xs=self.xs,width_type='linear',
|
||
sharp_patch=self.sharp_patch,
|
||
spiral_order=1).cell.put(flip=0).pin['b0']
|
||
|
||
else:
|
||
Ltp = np.max([0.5,np.abs(self.wd0-self.wd_in)/np.tan(self.tp_angle/180*pi)])
|
||
attach_in = Clothoid(R=[self.Rd0,self.Rd0,self.Rd0] , w=[self.wd0,self.wd0], A=[0,self.angle,0], dL_cal=0.001,dL_wg=self.res,
|
||
xs=self.xs,width_type='linear',end_patch=False,
|
||
sharp_patch=self.sharp_patch,spiral_order=1).cell.put(0,vtx_lower_y[0]/2+vtx_lower_y[-1]/2,180,flip=0)
|
||
|
||
# attach_in = circle(angle=self.angle,radius=self.Rd0,width=self.wd0+err,xs=self.xs).cell.put('a1',0,vtx_lower_y[0]/2+vtx_lower_y[-1]/2,180,flip=0)
|
||
# attach_in = circle(angle=self.angle,radius=self.Rd0,width=self.wd0+err,xs=self.xs).cell.put('b1',attach_in.pin['b1'],flip=0)
|
||
pin_a2=nd.taper(width1=self.wd0+err,width2=self.wd_in,length=Ltp,xs=self.xs).put(attach_in.pin['b1']).pin['b0']
|
||
patch = nd.strt(xs=self.xs,length=0.01,width=pin_a2.width).put()
|
||
|
||
else :
|
||
Ltp = np.max([5,np.abs(self.wd0-self.wd_in)/np.tan(self.tp_angle/180*pi)])
|
||
Ltp = int(Ltp*20)*0.05 ## keep it in integer
|
||
temp = nd.strt(xs=self.xs,length=5,width=self.wd0+err).put(0,vtx_lower_y[0]/2+vtx_lower_y[-1]/2,180,flip=0)
|
||
pin_a2 = nd.taper(xs=self.xs,length=Ltp,width1=self.wd0+err,width2=self.wd_in).put(temp.pin['b0']).pin['b0']
|
||
patch = nd.strt(xs=self.xs,length=0.01,width=pin_a2.width).put()
|
||
|
||
nd.Pin(name='a2',width=self.wd_in,pin=pin_a2).put()
|
||
|
||
if (self.Rd1!=0):
|
||
## placing the adiabatic bend attachment to lower waveguide, output port, label 1
|
||
if (self.sbend_type=='euler'):
|
||
attach_in = Clothoid(R=[self.Rd1,self.Rmin,self.Rd1] , w=[self.wd1,self.wd1], A=[0,self.angle/2,self.angle], dL_cal=0.001,dL_wg=self.res,
|
||
xs=self.xs,width_type='linear',
|
||
sharp_patch=self.sharp_patch,spiral_order=1)
|
||
attach_in.cell.put(self.Ld,vtx_lower_y[1]/2+vtx_lower_y[-2]/2,0,flip=1)
|
||
pin_b2 = Clothoid(R=[self.Rd1,self.Rmin,self.Rd1] , w=[self.wd1,self.wd_out], A=[self.angle,self.angle/2,0], dL_cal=0.001,dL_wg=self.res,
|
||
xs=self.xs,width_type='linear',
|
||
sharp_patch=self.sharp_patch,spiral_order=1).cell.put(flip=1).pin['b0']
|
||
else:
|
||
Ltp = np.max([0.5,np.abs(self.wd1+err-self.wd_out)/np.tan(self.tp_angle/180*pi)])
|
||
Ltp = int(Ltp*20)*0.05 ## keep it in integer
|
||
attach_in = Clothoid(R=[self.Rd1,self.Rd1,self.Rd1] , w=[self.wd1,self.wd1], A=[0,self.angle,0], dL_cal=0.001,dL_wg=self.res,
|
||
xs=self.xs,width_type='linear',end_patch=False,
|
||
sharp_patch=self.sharp_patch,spiral_order=1).cell.put(self.Ld,vtx_lower_y[1]/2+vtx_lower_y[-2]/2,0,flip=1)
|
||
|
||
# attach_in = circle(angle=self.angle,radius=self.Rd1,width=self.wd1+err,xs=self.xs).cell.put('a1',self.Ld,vtx_lower_y[1]/2+vtx_lower_y[-2]/2,0,flip=1)
|
||
# attach_in = circle(angle=self.angle,radius=self.Rd1,width=self.wd1+err,xs=self.xs).cell.put('b1',attach_in.pin['b1'],flip=1)
|
||
pin_b2=nd.taper(width1=self.wd1+err,width2=self.wd_out,length=Ltp,xs=self.xs).put(attach_in.pin['b1']).pin['b0']
|
||
patch = nd.strt(xs=self.xs,length=0.01,width=pin_b2.width).put()
|
||
|
||
_dX_ = abs(pin_b2.x - self.Ld)
|
||
else :
|
||
Ltp = np.abs(self.wd1+err-self.wd_out)/np.tan(self.tp_angle/180*pi)
|
||
Ltp = int(Ltp*20)*0.05 ## keep it in integer
|
||
temp = nd.strt(xs=self.xs,length=5,width=self.wd1+err).put(self.Ld,vtx_lower_y[1]/2+vtx_lower_y[-2]/2,0,flip=0)
|
||
|
||
pin_b2 = nd.taper(xs=self.xs,length=Ltp,width1=self.wd1+err,width2=self.wd_out).put().pin['b0']
|
||
patch = nd.strt(xs=self.xs,length=0.01,width=pin_b2.width).put()
|
||
|
||
nd.Pin(name='b2',width=self.wd_out,pin=pin_b2).put()
|
||
|
||
|
||
if (self.Ru0!=0):
|
||
if (self.sbend_type=='euler'):
|
||
## placing the adiabatic bend attachment to upper waveguide, input port, label 0
|
||
attach_in = Clothoid(R=[self.Ru0,self.Rmin,self.Ru0] , w=[self.wu0,self.wu0], A=[0,self.angle/2,self.angle], dL_cal=0.001,dL_wg=self.res,
|
||
xs=self.xs,width_type='linear',
|
||
sharp_patch=self.sharp_patch,spiral_order=1)
|
||
attach_in.cell.put(0,vtx_upper_y[0]/2+vtx_upper_y[-1]/2,180,flip=1)
|
||
pin_a1 = Clothoid(R=[self.Ru0,self.Rmin,self.Ru0] , w=[self.wu0,self.wu_in], A=[self.angle,self.angle/2,0], dL_cal=0.001,dL_wg=self.res,
|
||
xs=self.xs,width_type='linear',
|
||
sharp_patch=self.sharp_patch,spiral_order=1).cell.put(flip=1).pin['b0']
|
||
else:
|
||
Ltp = np.max([0.5,np.abs(self.wu0+err-self.wu_in)/np.tan(self.tp_angle/180*pi)])
|
||
Ltp = int(Ltp*20)*0.05 ## keep it in integer
|
||
attach_in = Clothoid(R=[self.Ru0,self.Ru0,self.Ru0] , w=[self.wu0,self.wu0], A=[0,self.angle,0], dL_cal=0.001,dL_wg=self.res,
|
||
xs=self.xs,width_type='linear',end_patch=False,
|
||
sharp_patch=self.sharp_patch,spiral_order=1).cell.put(0,vtx_upper_y[0]/2+vtx_upper_y[-1]/2,180,flip=1)
|
||
|
||
# attach_in = circle(angle=self.angle,radius=self.Ru0,width=self.wu0+err,xs=self.xs).cell.put('a1',0,vtx_upper_y[0]/2+vtx_upper_y[-1]/2,180,flip=1)
|
||
# attach_in = circle(angle=self.angle,radius=self.Ru0,width=self.wu0+err,xs=self.xs).cell.put('b1',attach_in.pin['b1'],flip=1)
|
||
|
||
pin_a1=nd.taper(width1=self.wu0+err,width2=self.wu_in,length=Ltp,xs=self.xs).put(attach_in.pin['b1']).pin['b0']
|
||
patch = nd.strt(xs=self.xs,length=0.01,width=pin_a1.width).put()
|
||
|
||
_dX_ = abs(pin_a1.x)
|
||
|
||
else :
|
||
Ltp = np.max([_dX_-5,np.abs(self.wu0+err-self.wu_in)/np.tan(self.tp_angle/180*pi)])
|
||
Ltp = int(Ltp*20)*0.05 ## keep it in integer
|
||
temp = nd.strt(xs=self.xs,length=5,width=self.wu0+err).put(0,vtx_upper_y[0]/2+vtx_upper_y[-1]/2,180,flip=0)
|
||
pin_a1 = nd.taper(xs=self.xs,length=Ltp,width1=self.wu0+err,width2=self.wu_in).put().pin['b0']
|
||
patch = nd.strt(xs=self.xs,length=0.01,width=pin_a1.width).put()
|
||
|
||
nd.Pin(name='a1',width=self.wu_in,pin=pin_a1).put()
|
||
|
||
if (self.Ru1!=0):
|
||
if (self.sbend_type=='euler'):
|
||
## placing the adiabatic bend attachment to upper waveguide, output port, label 1
|
||
attach_in = Clothoid(R=[self.Ru1,self.Rmin,self.Ru1] , w=[self.wu1,self.wu1], A=[0,self.angle/2,self.angle], dL_cal=0.001,dL_wg=self.res,
|
||
xs=self.xs,width_type='linear',
|
||
sharp_patch=self.sharp_patch,spiral_order=1)
|
||
attach_in.cell.put(self.Lu,vtx_upper_y[1]/2+vtx_upper_y[-2]/2,0,flip=0)
|
||
pin_b1=Clothoid(R=[self.Ru1,self.Rmin,self.Ru1] , w=[self.wu1,self.wu_in], A=[self.angle,self.angle/2,0], dL_cal=0.001,dL_wg=self.res,
|
||
xs=self.xs,width_type='linear',
|
||
sharp_patch=self.sharp_patch,spiral_order=1).cell.put(flip=0).pin['b0']
|
||
else: ## circular attachment
|
||
|
||
Ltp = np.max([0.5,np.abs(self.wu1+err-self.wu_out)/np.tan(self.tp_angle/180*pi)])
|
||
Ltp = int(Ltp*20)*0.05 ## keep it in integer
|
||
|
||
attach_in = Clothoid(R=[self.Ru1,self.Ru1,self.Ru1] , w=[self.wu1,self.wu1], A=[0,self.angle,0], dL_cal=0.001,dL_wg=self.res,
|
||
xs=self.xs,width_type='linear',end_patch=False,
|
||
sharp_patch=self.sharp_patch,spiral_order=1).cell.put(self.Lu,vtx_upper_y[1]/2+vtx_upper_y[-2]/2,0,flip=0)
|
||
|
||
# attach_in = circle(angle=self.angle,radius=self.Ru1,width=self.wu1+err,xs=self.xs).cell.put('a1',self.Lu,vtx_upper_y[1]/2+vtx_upper_y[-2]/2,0,flip=0)
|
||
# attach_in = circle(angle=self.angle,radius=self.Ru1,width=self.wu1+err,xs=self.xs).cell.put('b1',attach_in.pin['b1'],flip=0)
|
||
pin_b1=nd.taper(width1=self.wu1+err,width2=self.wu_out,length=Ltp,xs=self.xs).put(attach_in.pin['b1']).pin['b0']
|
||
patch = nd.strt(xs=self.xs,length=0.01,width=pin_b1.width).put()
|
||
else :
|
||
Ltp = np.max([5,np.abs(self.wu1+err-self.wu_out)/np.tan(self.tp_angle/180*pi)])
|
||
Ltp = int(Ltp*20)*0.05 ## keep it in integer
|
||
temp = nd.strt(xs=self.xs,length=5,width=self.wu1+err).put(self.Lu,vtx_upper_y[1]/2+vtx_upper_y[-2]/2,0,flip=0)
|
||
pin_b1 = nd.taper(xs=self.xs,length=Ltp,width1=self.wu1+err,width2=self.wu_out).put().pin['b0']
|
||
patch = nd.strt(xs=self.xs,length=0.01,width=pin_b1.width).put()
|
||
|
||
nd.Pin(name='b1',width=self.wu_out,pin=pin_b1).put()
|
||
|
||
dY1 = np.abs(pin_a1.y-pin_a2.y)
|
||
dX1 = np.abs(pin_a1.x-pin_a2.x)
|
||
dY2 = np.abs(pin_b1.y-pin_b2.y)
|
||
dX2 = np.abs(pin_b1.x-pin_b2.x)
|
||
|
||
# if (self.sharp_patch==True):
|
||
# for layers,growx,growy,acc in nd.layeriter(xs=self.xs):
|
||
# (a1,b1), (a2,b2),c1,c2 = growx
|
||
# if (b1!=0 and b2!=0):
|
||
# L_patch = (dX1+5)*(a1-a2)+(b1-b2)
|
||
# W_patch = (dY1+self.wu_in/2+self.wd_in/2)*(a1-a2)+(b1-b2)
|
||
# nd.strt(length=L_patch,width=W_patch,layer=layers).put(np.max([pin_a1.x,pin_a2.x]),(pin_a1.y+self.wu_in/2-self.wd_in/2+pin_a2.y)/2,180)
|
||
|
||
|
||
# L_patch = (dX2+5)*(a1-a2)+(b1-b2)
|
||
# W_patch = (dY2+self.wu_out/2+self.wd_out/2)*(a1-a2)+(b1-b2)
|
||
# nd.strt(length=L_patch,width=W_patch,layer=layers).put(np.min([pin_b1.x,pin_b2.x]),(pin_b1.y+self.wu_out/2-self.wd_out/2+pin_b2.y)/2,0)
|
||
|
||
if show_pins:
|
||
nd.put_stub(pinname='a1',pinsize=3)
|
||
nd.put_stub(pinname='a2',pinsize=3)
|
||
nd.put_stub(pinname='b1',pinsize=3)
|
||
nd.put_stub(pinname='b2',pinsize=3)
|
||
|
||
return C
|
||
|
||
def generate_err(self,err=0.02):
|
||
self.err = err
|
||
self.cell = self.generate_gds(err=err)
|
||
return self
|
||
|
||
def generate_test_gds(self,gc,dX_gc2gc=400,dY_gc2gc=80,sharp_patch=True):
|
||
with nd.Cell(instantiate=False) as C:
|
||
if (isinstance(gc,nd.Cell)):
|
||
gc_cell =gc
|
||
elif (hasattr(gc,'cell')):
|
||
gc_cell = gc.cell
|
||
else :
|
||
raise Exception("ERROR: In <mxpic::passive::ADC_STD_2x2::generate_test_gds>, <gc> is not recongized as a cell")
|
||
|
||
inst = self.cell.put('a1',-self.L/2,self.cell.pin['a1'].y,0)
|
||
pic_strip = Route(radius=15,width=self.wu_in,xs=self.xs)
|
||
|
||
GT_U_In = gc_cell.put('g1',-dX_gc2gc/2,dY_gc2gc/2,180)
|
||
nd.taper(width1=GT_U_In.pin['g1'].width,width2=self.wu_in,length=5,xs=self.xs).put(GT_U_In.pin['g1'])
|
||
pic_strip.sbend_p2p(original_function=not sharp_patch,pin2=inst.pin['a1']).put()
|
||
|
||
GT_D_In = gc_cell.put('g1',-dX_gc2gc/2,-dY_gc2gc/2,180)
|
||
nd.taper(width1=GT_D_In.pin['g1'].width,width2=self.wd_in,length=5,xs=self.xs).put(GT_D_In.pin['g1'])
|
||
pic_strip.sbend_p2p(original_function=not sharp_patch,pin2=inst.pin['a2'],width=self.wd_in).put()
|
||
|
||
GT_U_Out = gc_cell.put('g1', dX_gc2gc/2,dY_gc2gc/2,0)
|
||
nd.taper(width1=GT_U_Out.pin['g1'].width,width2=self.wu_out,length=5,xs=self.xs).put(GT_U_Out.pin['g1'])
|
||
pic_strip.sbend_p2p(original_function=not sharp_patch,pin2=inst.pin['b1'],width=self.wu_out).put()
|
||
|
||
GT_D_Out = gc_cell.put('g1', dX_gc2gc/2,-dY_gc2gc/2,0)
|
||
nd.taper(width1=GT_D_Out.pin['g1'].width,width2=self.wd_out,length=5,xs=self.xs).put(GT_D_Out.pin['g1'])
|
||
pic_strip.sbend_p2p(original_function=not sharp_patch,pin2=inst.pin['b2'],width=self.wd_out).put()
|
||
|
||
return C
|
||
|
||
|
||
class DC(ADC_STD_2x2):
|
||
"""
|
||
Standard symmetric directional coupler wrapper built on ``ADC_STD_2x2``.
|
||
|
||
Parameters
|
||
----------
|
||
name : str, optional
|
||
Unique cell identifier (default is None, meaning no instantiation).
|
||
xs : str, optional
|
||
Nazca cross-section key for both guides (default is "strip").
|
||
w_cp : float, optional
|
||
Coupling-section core width in microns (default is 0.45).
|
||
w_wg : float, optional
|
||
IO port width in microns (default is 0.45).
|
||
L_cp : float, optional
|
||
Coupling-section length in microns (default is 30).
|
||
angle : float, optional
|
||
Port bend deflection angle in degrees (default is 20).
|
||
gap : float, optional
|
||
Gap between the two cores in microns (default is 0.2).
|
||
sbend_type : str, optional
|
||
Type of the IO bend ("euler" or "circular", default is "circular").
|
||
Rmax : float, optional
|
||
Maximum Euler radius in microns when "sbend_type" is "euler" (default is None).
|
||
Rmin : int, optional
|
||
Minimum Euler radius in microns (default is 5).
|
||
R0 : int, optional
|
||
Circular bend radius in microns applied to both ports (default is 10).
|
||
tp_angle : int, optional
|
||
Straight taper half-angle in degrees when Euler bends are disabled (default is 2).
|
||
sharp_patch : bool, optional
|
||
Insert chamfer polygons to avoid acute corners (default is True).
|
||
show_pins : bool, optional
|
||
Draw Nazca stub markers for debugging (default is False).
|
||
"""
|
||
def __init__(self,
|
||
name = None,
|
||
xs:str='strip',
|
||
w_cp:float=0.45,
|
||
w_wg:float=0.45,
|
||
L_cp:float=30,
|
||
angle:float=20,
|
||
gap:float=0.2,
|
||
sbend_type:str='circular',
|
||
Rmax:float=None,
|
||
Rmin:float=5,
|
||
R0:float=10,
|
||
tp_angle:float=2,
|
||
sharp_patch:bool=True,
|
||
show_pins:bool=False):
|
||
|
||
super().__init__(name, xs, wu0=w_cp, wu1=w_cp,
|
||
wu_in=w_wg, wu_out=w_wg,
|
||
wd0=w_cp, wd1=w_cp,
|
||
wd_in=w_wg, wd_out=w_wg,
|
||
Lu=L_cp, Ld=L_cp, angle=angle,
|
||
g0=gap, g1=gap,
|
||
sbend_type=sbend_type, Rmax=Rmax, Rmin=Rmin,
|
||
Ru0=R0, Ru1=R0, Rd0=R0, Rd1=R0, tp_angle=tp_angle, sharp_patch=sharp_patch,show_pins=show_pins)
|
||
|
||
def generate_test_gds(self,gc,dX_gc2gc=300,dY_gc2gc=40,sharp_patch=True):
|
||
with nd.Cell(name=self.cell.cell_name+"_test", instantiate=False) as C:
|
||
|
||
gc_cell = __cell_arg__(arg=gc,arg_name="gc",func_name="mxpic::DC::generate_test_gds")
|
||
|
||
gc_ID = gc_cell.put('g1',0,-dY_gc2gc,180)
|
||
gc_IU = gc_cell.put('g1',0,0,180)
|
||
gc_OU = gc_cell.put('g1',dX_gc2gc,-dY_gc2gc,0)
|
||
gc_OD = gc_cell.put('g1',dX_gc2gc,0,0)
|
||
# Put DC
|
||
inst = self.cell.put('a1',-self.L/2+dX_gc2gc/2,self.cell.pin['a1'].y-dY_gc2gc/2,0)
|
||
|
||
# Connect all the ports
|
||
stripe=Route(radius=self.Ru0, width=self.wu_in, xs="strip")
|
||
|
||
if (abs(inst.pin['b1'].y - inst.pin['b2'].y)<10) :
|
||
temp = stripe.sbend_route(pin=inst.pin['a1'],offset=5).put(flip=1)
|
||
stripe.sbend_p2p(pin1=gc_IU.pin['g1'],pin2=temp.pin['b0'],arrow=False,original_function=not sharp_patch).put()
|
||
|
||
temp = stripe.sbend_route(pin=inst.pin['a2'],offset=5).put()
|
||
stripe.sbend_p2p(pin1=gc_ID.pin['g1'],pin2=temp.pin['b0'],arrow=False,original_function=not sharp_patch).put()
|
||
|
||
temp = stripe.sbend_route(pin=inst.pin['b1'],offset=5).put()
|
||
stripe.sbend_p2p(pin1=gc_OD.pin['g1'],pin2=temp.pin['b0'],arrow=False,original_function=not sharp_patch).put()
|
||
|
||
temp = stripe.sbend_route(pin=inst.pin['b2'],offset=5).put(flip=1)
|
||
stripe.sbend_p2p(pin1=gc_OU.pin['g1'],pin2=temp.pin['b0'],arrow=False,original_function=not sharp_patch).put()
|
||
else :
|
||
stripe.sbend_p2p(pin1=gc_IU.pin['g1'],pin2=inst.pin['a1'],arrow=False,original_function=not sharp_patch).put()
|
||
stripe.sbend_p2p(pin1=gc_ID.pin['g1'],pin2=inst.pin['a2'],arrow=False,original_function=not sharp_patch).put()
|
||
stripe.sbend_p2p(pin1=gc_OD.pin['g1'],pin2=inst.pin['b1'],arrow=False,original_function=not sharp_patch).put()
|
||
stripe.sbend_p2p(pin1=gc_OU.pin['g1'],pin2=inst.pin['b2'],arrow=False,original_function=not sharp_patch).put()
|
||
return C
|
||
|
||
class BS_tdc(ADC_STD_2x2):
|
||
"""
|
||
Balanced splitter based on asymmetric taper-directional-coupler sections.
|
||
|
||
Parameters
|
||
----------
|
||
name : str, optional
|
||
Nazca cell name (default is None).
|
||
xs : str, optional
|
||
Nazca cross-section for both guides (default is "strip").
|
||
wa0 : float, optional
|
||
Upper input width in microns (default is 0.35).
|
||
wa1 : float, optional
|
||
Upper output width in microns (default is 0.45).
|
||
wb0 : float, optional
|
||
Lower input width in microns (default is 0.55).
|
||
wb1 : float, optional
|
||
Lower output width in microns (default is 0.45).
|
||
w_wg : float, optional
|
||
External IO width in microns (default is 0.45).
|
||
gap : float, optional
|
||
Coupling gap in microns (default is 0.2).
|
||
Lt : float, optional
|
||
Coupling/taper length in microns (default is 20).
|
||
R0 : float, optional
|
||
Port bend radius in microns (default is 30).
|
||
angle : float, optional
|
||
Port bend angle in degrees (default is 15).
|
||
sbend_type : str, optional
|
||
IO bend type ("circle" or "euler", default is "circle").
|
||
"""
|
||
def __init__(self,
|
||
name=None,
|
||
xs:str ='strip',
|
||
wa0:float = 0.35,
|
||
wa1:float = 0.45,
|
||
wb0:float = 0.55,
|
||
wb1:float = 0.45,
|
||
w_wg:float = 0.45,
|
||
gap:float =0.2,
|
||
Lt:float =20,
|
||
R0:float =30,
|
||
angle:float =15,
|
||
sbend_type:str ='circle',
|
||
):
|
||
|
||
super().__init__(name = name,
|
||
xs=xs,
|
||
wu0=wa0,wu1=wa1,wu_in=w_wg,wu_out=w_wg,
|
||
wd0=wb0,wd1=wb1,wd_in=w_wg,wd_out=w_wg,
|
||
g0=gap,g1=gap,
|
||
Ru0=R0,Ru1=R0,Rmin=5,
|
||
Rd0=R0,Rd1=R0,angle=angle,
|
||
Ld=Lt,Lu=Lt,
|
||
sbend_type=sbend_type)
|
||
|
||
|
||
class MDM(ADC_STD_2x2):
|
||
"""
|
||
Mode-division-multiplexing directional coupler derived from ``ADC_STD_2x2``.
|
||
|
||
Parameters
|
||
----------
|
||
name : str, optional
|
||
Nazca cell name (default is None).
|
||
xs : str, optional
|
||
Device cross-section key (default is "strip").
|
||
wb0 : float, optional
|
||
BUS waveguide width at the input plane in microns (default is 0.45).
|
||
wb1 : float, optional
|
||
BUS waveguide width at the output plane in microns (default is 0.61).
|
||
wb_in : float, optional
|
||
BUS input port width in microns (default is 0.45).
|
||
wb_out : float, optional
|
||
BUS output port width in microns (default is 0.88).
|
||
w_wg : float, optional
|
||
Coupler-waveguide IO width in microns (default is 0.45).
|
||
w0 : float, optional
|
||
Coupler waveguide width at the input plane in microns (default is 0.33).
|
||
w1 : float, optional
|
||
Coupler waveguide width at the output plane in microns (default is 0.2).
|
||
gap0 : float, optional
|
||
Initial BUS–coupler gap in microns (default is 0.2).
|
||
Lt_bus : float, optional
|
||
BUS taper length from wb0 to wb1 in microns (default is 20).
|
||
R0 : float, optional
|
||
Lower-waveguide bend radius in microns (default is 40).
|
||
angle : float, optional
|
||
Bend deflection angle in degrees (default is 22.5).
|
||
Lt_cp : float, optional
|
||
Coupler taper length from w0 to w1 in microns (default is None, meaning ``Lt_bus``).
|
||
gap1 : float, optional
|
||
Final BUS–coupler gap in microns (default is None, meaning ``gap0``).
|
||
Lb0 : float, optional
|
||
Reserved for future BUS offsets (default is None).
|
||
symmetric_BUS : bool, optional
|
||
Whether BUS geometry is mirrored (default is True).
|
||
single_end : bool, optional
|
||
Keep single-ended termination on the coupler arm (default is True).
|
||
Rmin : float, optional
|
||
Minimum Euler radius in microns for bends (default is 8).
|
||
"""
|
||
def __init__(self,
|
||
name = None,
|
||
xs:str='strip',
|
||
wb0:float =0.45,
|
||
wb1:float =0.61,
|
||
wb_in:float =0.45,
|
||
wb_out:float =0.88,
|
||
w_wg:float =0.45,
|
||
w0:float =0.33,
|
||
w1:float =0.2,
|
||
gap0:float =0.2,
|
||
Lt_bus:float =20,
|
||
R0:float =40,
|
||
angle:float =22.5,
|
||
Lt_cp:float =None,
|
||
gap1:float =None,
|
||
Lb0:float =None,
|
||
symmetric_BUS:bool =True,
|
||
single_end:bool =True,
|
||
Rmin:float =8
|
||
):
|
||
|
||
self.wb0=wb0 ## BUS waveguide width on the input
|
||
|
||
if (wb1!=None): ## BUS waveguide width on the output
|
||
self.wb1=wb1
|
||
else:
|
||
self.wb1=wb0
|
||
wb1 = wb0
|
||
self.w0=w0
|
||
if (w1!=None):
|
||
self.w1=w1
|
||
else:
|
||
self.w1=w0
|
||
w1= w0
|
||
self.w_wg=w_wg
|
||
self.gap0=gap0
|
||
if (gap1!=None):
|
||
self.gap1=gap1
|
||
else:
|
||
self.gap1=gap0
|
||
gap1 = gap0
|
||
|
||
if (Lt_cp==None):
|
||
Lt_cp = Lt_bus
|
||
|
||
self.Lt_bus = Lt_bus
|
||
self.Lt_cp = Lt_cp
|
||
|
||
self.xs = xs
|
||
self.Lt_cp = Lt_cp
|
||
self.R0 = R0
|
||
self.angle = angle
|
||
self.symmetric_BUS = symmetric_BUS ## defining the type of bus waveguide
|
||
self.Rmin = Rmin
|
||
super().__init__(name = name,xs=xs,
|
||
wu0=wb0,wu1=wb1,wu_in=wb_in,wu_out=wb_out,
|
||
wd0=w0,wd1=w1,wd_in=w_wg,wd_out=w1,
|
||
Lu=Lt_bus,Ld=Lt_cp,
|
||
g0=gap0,g1=gap1,
|
||
Ru0=0,Ru1=0,
|
||
Rmin=self.Rmin,
|
||
Rd0=R0,Rd1=R0,
|
||
angle=angle)
|
||
|
||
self.L = np.abs(self.cell.pin['a1'].x-self.cell.pin['b1'].x)
|
||
|
||
def generate_test_gds(self,gc,dX_gc2gc=300,dY_gc2gc=40,sharp_patch=True):
|
||
with nd.Cell(name=self.cell.cell_name+"_test", instantiate=False) as C:
|
||
|
||
gc_cell = __cell_arg__(arg=gc,arg_name="gc",func_name="mxpic::MDM::generate_test_gds")
|
||
|
||
# Put DC
|
||
L_taper = (np.abs(self.cell.pin['a1'].width-gc_cell.pin['g1'].width))/np.tan(2/180*pi)
|
||
|
||
mdm_In = self.cell.put('b1',-dX_gc2gc/2 + self.L + 25+L_taper,0,180)
|
||
mdm_Out = self.cell.put('b1', dX_gc2gc/2 - self.L - 25-L_taper,0,0,flip=1)
|
||
|
||
|
||
GC_IU = gc_cell.put('g1',-dX_gc2gc/2,dY_gc2gc/2,180)
|
||
GC_ID = gc_cell.put('g1',-dX_gc2gc/2,-dY_gc2gc/2,180)
|
||
GC_OU = gc_cell.put('g1', dX_gc2gc/2,dY_gc2gc/2,0)
|
||
GC_OD = gc_cell.put('g1', dX_gc2gc/2,-dY_gc2gc/2,0)
|
||
|
||
# # Connect all the ports
|
||
stripe=Route(radius=10, width=self.w_wg, xs="strip")
|
||
nd.taper(width1=mdm_In.pin['a1'].width,width2=gc_cell.pin['g1'].width,length=L_taper,xs='strip').put(mdm_In.pin['a1'])
|
||
stripe.sbend_p2p(pin2=GC_IU.pin['g1'],arrow=False,original_function=not sharp_patch).put()
|
||
|
||
stripe.sbend_p2p(pin1=GC_ID.pin['g1'],pin2=mdm_In.pin['a2'],arrow=False,original_function=not sharp_patch).put()
|
||
|
||
nd.taper(width1=mdm_Out.pin['a1'].width,width2=gc_cell.pin['g1'].width,length=L_taper,xs='strip').put(mdm_Out.pin['a1'])
|
||
stripe.sbend_p2p(pin1=GC_OU.pin['g1'],arrow=False,original_function=not sharp_patch).put()
|
||
stripe.sbend_p2p(pin1=GC_OD.pin['g1'],pin2=mdm_Out.pin['a2'],arrow=False,original_function=not sharp_patch).put()
|
||
|
||
stripe.taper_p2p(pin1=mdm_In.pin['b1'],pin2=mdm_Out.pin['b1'],arrow=False).put()
|
||
|
||
return C
|
||
|
||
|
||
|
||
|
||
class DC_bend :
|
||
'''
|
||
This is a class for bend directional coupler for broadband and fabrication tolerant power splitting.
|
||
Written by HU GAOLEI at 2022.5.15.
|
||
'''
|
||
|
||
"""
|
||
Bend-based directional coupler for broadband, fabrication-tolerant splitting.
|
||
|
||
Parameters
|
||
----------
|
||
name : str, optional
|
||
Nazca cell name (default is None).
|
||
w_in : float, optional
|
||
Inner (tight) waveguide width in the coupling region, in microns (default is 0.45).
|
||
w_out : float, optional
|
||
Outer waveguide width in the coupling region, in microns (default is 0.45).
|
||
gap : float, optional
|
||
Separation between waveguides in microns (default is 0.2).
|
||
r_in : float, optional
|
||
Bend radius of the inner waveguide in microns (default is 40).
|
||
theta_arc : float, optional
|
||
Coupling-arc angle in degrees (default is 30).
|
||
w_wg : float, optional
|
||
IO waveguide width in microns (default is 0.45).
|
||
theta_ext : float, optional
|
||
Extra bend angle used to align IO planes in degrees (default is 15).
|
||
xs_wg : str, optional
|
||
Nazca cross-section for both waveguides (default is "strip").
|
||
sharp_patch : bool, optional
|
||
Insert chamfer polygons to smooth acute corners (default is True).
|
||
show_pins : bool, optional
|
||
Draw Nazca stub markers for debugging (default is False).
|
||
"""
|
||
|
||
|
||
def __init__(
|
||
self,
|
||
name = None,
|
||
w_in=0.45,
|
||
w_out=0.45,
|
||
gap=0.2,
|
||
r_in=40,
|
||
theta_arc=30,
|
||
w_wg=0.45,
|
||
theta_ext=15,
|
||
xs_wg='strip',
|
||
|
||
sharp_patch=True,
|
||
show_pins=False
|
||
):
|
||
|
||
self.name = name
|
||
if (self.name==None):
|
||
self.instantiate = False
|
||
else :
|
||
self.instantiate = True
|
||
|
||
self.w_in = w_in
|
||
self.w_out = w_out
|
||
self.gap = gap
|
||
self.r_in = r_in
|
||
self.r_out = self.r_in+(self.w_in+self.w_out)/2+self.gap
|
||
self.theta_arc = theta_arc
|
||
self.theta_ext = theta_ext
|
||
self.w_wg = w_wg
|
||
self.xs_wg = xs_wg
|
||
self.sharp_patch = sharp_patch
|
||
self.show_pins = show_pins
|
||
self.cell = self.generate_gds(name)
|
||
|
||
def generate_gds(self, cellname=""):
|
||
'''
|
||
Generate GDS.
|
||
'''
|
||
with nd.Cell(name="DC_Bend"+str(cellname), instantiate=self.instantiate) as C:
|
||
wg = Route(width=self.w_wg, radius=10, xs=self.xs_wg)
|
||
## Put Outer Bend Region first
|
||
|
||
bent_out_coup_r = wg.bend(width=self.w_out, radius=self.r_out, angle=self.theta_arc/2, arrow=False).put(0, 0, 0)
|
||
bend_out_connect_r = wg.bend(width=self.w_out, radius=self.r_out, angle=-self.theta_arc/2, arrow=False).put(bent_out_coup_r.pin['b0'])
|
||
|
||
bent_out_coup_l = wg.bend(width=self.w_out, radius=self.r_out, angle=self.theta_arc/2, arrow=False).put(0, 0, 0, flop=True)
|
||
bend_out_connect_l = wg.bend(width=self.w_out, radius=self.r_out, angle=self.theta_arc/2, arrow=False).put(bent_out_coup_l.pin['b0'])
|
||
## Put Inner Bend Region
|
||
bent_in_coup_r = wg.bend(width=self.w_in, radius=self.r_in, angle=self.theta_arc/2, arrow=False).put(0, (self.w_in+self.w_out)/2+self.gap, 0)
|
||
wg.bend(width=self.w_in, radius=self.r_in, angle=self.theta_ext, arrow=False).put(bent_in_coup_r.pin['b0'])
|
||
bend_in_connect_r = wg.bend(width=self.w_in, angle=-self.theta_arc/2-self.theta_ext, arrow=False).put()
|
||
|
||
bend_in_coup_l = wg.bend(width=self.w_in, radius=self.r_in, angle=self.theta_arc/2, arrow=False).put(0, (self.w_in+self.w_out)/2+self.gap, 0, flop=True)
|
||
wg.bend(width=self.w_in, radius=self.r_in, angle=-self.theta_ext, arrow=False).put(bend_in_coup_l.pin['b0'])
|
||
bend_in_connect_l = wg.bend(width=self.w_in, angle=self.theta_arc/2+self.theta_ext, arrow=False).put()
|
||
|
||
## Make upper and lower waveguide's output at the same x-plane
|
||
if bend_out_connect_r.pin['b0'].x > bend_in_connect_r.pin['b0'].x:
|
||
l_extra = bend_out_connect_r.pin['b0'].x-bend_in_connect_r.pin['b0'].x
|
||
bend_in_connect_r = wg.strt(length=l_extra, width=self.w_in, arrow=False).put(bend_in_connect_r.pin['b0'])
|
||
bend_in_connect_l = wg.strt(length=l_extra, width=self.w_in, arrow=False).put(bend_in_connect_l.pin['b0'])
|
||
elif bend_out_connect_r.pin['b0'].x < bend_in_connect_r.pin['b0'].x:
|
||
l_extra = bend_in_connect_r.pin['b0'].x-bend_out_connect_r.pin['b0'].x
|
||
bend_out_connect_r = wg.strt(length=l_extra, width=self.w_out, arrow=False).put(bend_out_connect_r.pin['b0'])
|
||
bend_out_connect_l = wg.strt(length=l_extra, width=self.w_out, arrow=False).put(bend_out_connect_l.pin['b0'])
|
||
|
||
## Add taper to make the width in the coupling region connect with normal wg
|
||
l_taper = 2
|
||
port_out1 = wg.taper(width1=self.w_in, width2=self.w_wg, length=l_taper, arrow=False).put(bend_in_connect_r.pin['b0'])
|
||
port_in1 = wg.taper(width1=self.w_in, width2=self.w_wg, length=l_taper, arrow=False).put(bend_in_connect_l.pin['b0'])
|
||
port_out2 = wg.taper(width1=self.w_out, width2=self.w_wg, length=l_taper, arrow=False).put(bend_out_connect_r.pin['b0'])
|
||
port_in2 = wg.taper(width1=self.w_out, width2=self.w_wg, length=l_taper, arrow=False).put(bend_out_connect_l.pin['b0'])
|
||
|
||
## Put pins
|
||
nd.Pin(name="a0", width=self.w_wg).put((port_in1.pin['b0'].x+port_in2.pin['b0'].x)/2, (port_in1.pin['b0'].y+port_in2.pin['b0'].y)/2, 180)
|
||
nd.Pin(name="a1", width=self.w_wg).put(port_in1.pin['b0'])
|
||
nd.Pin(name="a2", width=self.w_wg).put(port_in2.pin['b0'])
|
||
nd.Pin(name="b1", width=self.w_wg).put(port_out1.pin['b0'])
|
||
nd.Pin(name="b2", width=self.w_wg).put(port_out2.pin['b0'])
|
||
self.width = np.abs(port_out1.pin['b0'].y - port_out2.pin['b0'].y)
|
||
self.length = np.abs(port_out1.pin['b0'].x - port_in1.pin['b0'].x)
|
||
if self.show_pins:
|
||
nd.put_stub()
|
||
|
||
pin_a1 = port_in1.pin['b0']
|
||
pin_a2 = port_in2.pin['b0']
|
||
pin_b1 = port_out1.pin['b0']
|
||
pin_b2 = port_out2.pin['b0']
|
||
dY1 = np.abs(pin_a1.y-pin_a2.y)
|
||
dX1 = np.abs(pin_a1.x-pin_a2.x)
|
||
dY2 = np.abs(pin_b1.y-pin_b2.y)
|
||
dX2 = np.abs(pin_b1.x-pin_b2.x)
|
||
|
||
if (self.sharp_patch==True):
|
||
for layers,growx,growy,acc in nd.layeriter(xs=self.xs_wg):
|
||
(a1,b1), (a2,b2),c1,c2 = growx
|
||
if (b1!=0 and b2!=0):
|
||
L_patch = (dX1+5)*(a1-a2)+(b1-b2)
|
||
W_patch = (dY1+self.w_wg)*(a1-a2)+(b1-b2)
|
||
nd.strt(length=L_patch,width=W_patch,layer=layers).put(np.max([pin_a1.x,pin_a2.x]),(pin_a1.y+pin_a2.y)/2,180)
|
||
|
||
|
||
L_patch = (dX2+5)*(a1-a2)+(b1-b2)
|
||
W_patch = (dY2+self.w_wg)*(a1-a2)+(b1-b2)
|
||
nd.strt(length=L_patch,width=W_patch,layer=layers).put(np.min([pin_b1.x,pin_b2.x]),(pin_b1.y+pin_b2.y)/2,0)
|
||
return C
|
||
|
||
def generate_test_gds(self,gc,dX_gc2gc=300,dY_gc2gc=40,sharp_patch=True):
|
||
with nd.Cell(name=self.cell.cell_name+"_test", instantiate=False) as C:
|
||
|
||
gc_cell = __cell_arg__(arg=gc,arg_name="gc",func_name="mxpic::DC_bend::generate_test_gds")
|
||
|
||
# gc_ID = gc_cell.put('g1',-dX_gc2gc/2,-dY_gc2gc/2,180)
|
||
# gc_IU = gc_cell.put('g1',-dX_gc2gc/2,dY_gc2gc/2,180)
|
||
# gc_OU = gc_cell.put('g1',dX_gc2gc/2,-dY_gc2gc/2,0)
|
||
# gc_OD = gc_cell.put('g1',dX_gc2gc/2,dY_gc2gc/2,0)
|
||
# # Put DC
|
||
# dc = self.cell.put('a1',-self.length/2,0,0)
|
||
|
||
gc_ID = gc_cell.put('g1',0,-dY_gc2gc,180)
|
||
gc_IU = gc_cell.put('g1',0,0,180)
|
||
gc_OU = gc_cell.put('g1',dX_gc2gc,-dY_gc2gc,0)
|
||
gc_OD = gc_cell.put('g1',dX_gc2gc,0,0)
|
||
# Put DC
|
||
dL_DC = self.cell.pin['b1'].x - self.cell.pin['a1'].x
|
||
inst = self.cell.put('a1',-dL_DC/2+dX_gc2gc/2,self.cell.pin['a1'].y-dY_gc2gc/2,0)
|
||
|
||
# Connect all the ports
|
||
stripe=Route(radius=10, width=self.w_wg, xs="strip")
|
||
stripe.sbend_p2p(pin1=gc_IU.pin['g1'],pin2=inst.pin['a1'],arrow=False,original_function=not sharp_patch).put()
|
||
stripe.sbend_p2p(pin1=gc_ID.pin['g1'],pin2=inst.pin['a2'],arrow=False,original_function=not sharp_patch).put()
|
||
stripe.sbend_p2p(pin1=gc_OD.pin['g1'],pin2=inst.pin['b1'],arrow=False,original_function=not sharp_patch).put()
|
||
stripe.sbend_p2p(pin1=gc_OU.pin['g1'],pin2=inst.pin['b2'],arrow=False,original_function=not sharp_patch).put()
|
||
return C
|
||
|
||
|
||
class DC_pX_3sg:
|
||
"""
|
||
Three-segment phase-tunable directional coupler (pX) generator.
|
||
|
||
Parameters
|
||
----------
|
||
name : str, optional
|
||
Nazca cell name (default is None).
|
||
xs_wg : str, optional
|
||
Cross-section key for all segments (default is "strip").
|
||
Lc1 : float, optional
|
||
Length of the first coupling segment in microns (default is 10).
|
||
Lp1 : float, optional
|
||
Phase-shifter length in microns (default is 5).
|
||
Lc2 : float, optional
|
||
Length of the second coupling segment in microns (default is 10).
|
||
Lt : float, optional
|
||
Taper length between coupling and phase sections in microns (default is 1).
|
||
w_cp : float, optional
|
||
Nominal coupling width in microns (default is 0.5).
|
||
dw : float, optional
|
||
Width offset applied to the phase section in microns (default is 0.1).
|
||
gap : float, optional
|
||
Vertical spacing between the two cores in microns (default is 0.2).
|
||
R0 : float, optional
|
||
Bend radius in microns for port transitions (default is 10).
|
||
A : float, optional
|
||
Bend angle in degrees for port transitions (default is 15).
|
||
w_wg : float, optional
|
||
External IO width in microns (default is 0.45).
|
||
pX_type : str, optional
|
||
Phase-section topology ("symmetric" or "asymmetric", default is "symmetric").
|
||
port_symmetric : bool, optional
|
||
Use mirrored port routing for both arms (default is True).
|
||
sharp_patch : bool, optional
|
||
Insert chamfer polygons to mitigate sharp tips (default is True).
|
||
"""
|
||
|
||
def __init__(self,
|
||
name = None,
|
||
xs_wg:str='strip',
|
||
Lc1:float=10,
|
||
Lp1:float=5,
|
||
Lc2:float=10,
|
||
Lt:float=1,
|
||
w_cp:float=0.5,
|
||
dw:float=0.1,
|
||
gap:float=0.2,
|
||
R0:float=10,
|
||
A:float=15,
|
||
w_wg:float=0.45,
|
||
pX_type:str="symmetric",
|
||
port_symmetric:bool=True,
|
||
sharp_patch:bool=True):
|
||
|
||
self.name = name
|
||
if (self.name==None):
|
||
self.instantiate = False
|
||
else :
|
||
self.instantiate = True
|
||
|
||
self.xs_wg=xs_wg
|
||
self.Lc1=Lc1
|
||
self.Lp1=Lp1
|
||
self.Lc2=Lc2
|
||
self.Lt=Lt
|
||
self.w_cp=w_cp
|
||
self.dw=dw
|
||
self.gap=gap
|
||
self.R0=R0
|
||
self.A=A
|
||
self.w_wg=w_wg
|
||
self.sharp_patch=sharp_patch
|
||
self.pX_type=pX_type
|
||
self.port_symmetric=port_symmetric
|
||
cells = self.generate_gds(err=0)
|
||
self.cell = cells[0]
|
||
self.cellU = cells[2]
|
||
self.cellD = cells[1]
|
||
|
||
self.L = np.abs(self.cell.pin['a1'].x - self.cell.pin['b1'].x)
|
||
self.length = self.L
|
||
|
||
def generate_gds(self,err=0):
|
||
if (self.name is not None):
|
||
nameUP = self.name + "_up"
|
||
nameDOWN = self.name + "_down"
|
||
else:
|
||
nameUP = None
|
||
nameDOWN = None
|
||
|
||
w_cp = self.w_cp + err
|
||
gap = self.gap - err
|
||
|
||
with nd.Cell(instantiate=False) as CUP:
|
||
|
||
## first segment coupler
|
||
cp_u = nd.strt(length=self.Lc1,width=w_cp,xs=self.xs_wg).put(0, w_cp/2+gap/2,0)
|
||
nd.Pin(name='a0').put(cp_u.pin['a0'])
|
||
|
||
cp_u_r = nd.bend(radius=self.R0,angle=self.A,xs=self.xs_wg,width=w_cp).put(cp_u.pin['a0'],flip=1)
|
||
cp_u_r = nd.bend(radius=self.R0,angle=self.A,xs=self.xs_wg,width=w_cp).put(flip=0)
|
||
cp_u_r = nd.taper(width1=w_cp,width2=self.w_wg,xs=self.xs_wg,length=2).put()
|
||
|
||
nd.Pin(name='a1',pin=cp_u_r.pin['b0'],width=cp_u_r.pin['b0'].width).put()
|
||
|
||
## middle segment phase shifter
|
||
if self.pX_type == "symmetric":
|
||
cp_u = nd.taper(length=self.Lt,width1=self.w_cp,width2=self.w_cp+self.dw,shift=0,xs=self.xs_wg).put(cp_u.pin['b0'])
|
||
cp_u = nd.strt(length=self.Lp1,width=w_cp+self.dw,xs=self.xs_wg).put()
|
||
cp_u = nd.taper(length=self.Lt,width1=self.w_cp+self.dw,width2=self.w_cp,shift=0,xs=self.xs_wg).put()
|
||
|
||
else:
|
||
cp_u = nd.taper(length=self.Lt,width1=self.w_cp,width2=self.w_cp+self.dw,shift=self.dw/2,xs=self.xs_wg).put(cp_u.pin['b0'])
|
||
cp_u = nd.strt(length=self.Lp1,width=w_cp+self.dw,xs=self.xs_wg).put()
|
||
cp_u = nd.taper(length=self.Lt,width1=self.w_cp+self.dw,width2=self.w_cp,shift=-self.dw/2,xs=self.xs_wg).put()
|
||
## second segment coupler
|
||
cp_u = nd.strt(length=self.Lc2,width=w_cp,xs=self.xs_wg).put(cp_u.pin['b0'])
|
||
|
||
cp_u_r = nd.bend(radius=self.R0,angle=self.A,xs=self.xs_wg,width=w_cp).put(cp_u.pin['b0'])
|
||
cp_u_r = nd.bend(radius=self.R0,angle=self.A,xs=self.xs_wg,width=w_cp).put(flip=1)
|
||
cp_u_r = nd.taper(width1=w_cp,width2=self.w_wg,xs=self.xs_wg,length=2).put()
|
||
|
||
nd.Pin(name='b1',pin=cp_u_r.pin['b0'],width=cp_u_r.pin['b0'].width).put()
|
||
|
||
nd.Pin(name='b0').put(cp_u.pin['b0'])
|
||
|
||
|
||
with nd.Cell(instantiate=False) as CDOWN:
|
||
## first segment coupler
|
||
cp_d = nd.strt(length=self.Lc1,width=w_cp,xs=self.xs_wg).put(0,-(w_cp/2+gap/2),0)
|
||
nd.Pin(name='a0').put(cp_d.pin['a0'])
|
||
|
||
cp_d_r = nd.bend(radius=self.R0,angle=self.A,xs=self.xs_wg,width=w_cp).put(cp_d.pin['a0'],flip=0)
|
||
|
||
if (self.port_symmetric):
|
||
cp_d_r = nd.bend(radius=self.R0,angle=self.A,xs=self.xs_wg,width=w_cp).put(flip=1)
|
||
cp_d_r = nd.taper(width1=w_cp,width2=self.w_wg,xs=self.xs_wg,length=2).put()
|
||
else:
|
||
cp_d_r = nd.bend(radius=self.R0,angle=self.A,xs=self.xs_wg,width=w_cp).put()
|
||
# cp_d_r = nd.taper(width1=w_cp,width2=self.w_wg,xs=self.xs_wg,length=2).put()
|
||
|
||
nd.Pin(name='a2',pin=cp_d_r.pin['b0'],width=cp_d_r.pin['b0'].width).put()
|
||
|
||
## middle segment phase shifter
|
||
if self.pX_type == "symmetric":
|
||
|
||
cp_d = nd.taper(length=self.Lt,width1=self.w_cp,width2=self.w_cp-self.dw,shift=0,xs=self.xs_wg).put(cp_d.pin['b0'])
|
||
cp_d = nd.strt(length=self.Lp1,width=w_cp-self.dw,xs=self.xs_wg).put()
|
||
cp_d = nd.taper(length=self.Lt,width1=self.w_cp-self.dw,width2=self.w_cp,shift=0,xs=self.xs_wg).put()
|
||
|
||
else:
|
||
cp_d = nd.taper(length=self.Lt,width1=self.w_cp,width2=self.w_cp-self.dw,shift=-self.dw/2,xs=self.xs_wg).put(cp_d.pin['b0'])
|
||
cp_d = nd.strt(length=self.Lp1,width=w_cp-self.dw,xs=self.xs_wg).put()
|
||
cp_d = nd.taper(length=self.Lt,width1=self.w_cp-self.dw,width2=self.w_cp,shift=self.dw/2,xs=self.xs_wg).put()
|
||
|
||
## second segment coupler
|
||
cp_d = nd.strt(length=self.Lc2,width=w_cp,xs=self.xs_wg).put(cp_d.pin['b0'])
|
||
|
||
cp_d_r = nd.bend(radius=self.R0,angle=self.A,xs=self.xs_wg,width=w_cp).put(cp_d.pin['b0'],flip=1)
|
||
if (self.port_symmetric):
|
||
cp_d_r = nd.bend(radius=self.R0,angle=self.A,xs=self.xs_wg,width=w_cp).put(flip=0)
|
||
cp_d_r = nd.taper(width1=w_cp,width2=self.w_wg,xs=self.xs_wg,length=2).put()
|
||
else:
|
||
cp_d_r = nd.bend(radius=self.R0,angle=self.A,xs=self.xs_wg,width=w_cp).put(flip=1)
|
||
|
||
nd.Pin(name='b2',pin=cp_d_r.pin['b0'],width=cp_d_r.pin['b0'].width).put()
|
||
nd.Pin(name='b0').put(cp_d.pin['b0'])
|
||
|
||
|
||
with nd.Cell(instantiate=self.instantiate,name=self.name) as C:
|
||
wgUp = CUP.put(0,(w_cp/2+gap/2),0)
|
||
wgDown = CDOWN.put(0,-(w_cp/2+gap/2),0)
|
||
|
||
wgUp.raise_pins(['a1','b1'],['a1','b1'])
|
||
wgDown.raise_pins(['a2','b2'],['a2','b2'])
|
||
nd.Pin(name='a0').put((self.Lc1+self.Lc2+self.Lp1+self.Lt*2)/2,0,180)
|
||
nd.Pin(name='b0').put((self.Lc1+self.Lc2+self.Lp1+self.Lt*2)/2,0,0)
|
||
|
||
return (C,CDOWN,CUP)
|
||
|
||
def generate_test_gds(self,gc,dX_gc2gc=300,dY_gc2gc=40,sharp_patch=True):
|
||
with nd.Cell(name=self.cell.cell_name+"_test", instantiate=False) as C:
|
||
|
||
gc_cell = __cell_arg__(arg=gc,arg_name="gc",func_name="mxpic::DC_pX_3sg::generate_test_gds")
|
||
|
||
# Put DC
|
||
|
||
# GC_IU = gc_cell.put('g1',-dX_gc2gc/2,dY_gc2gc/2,180)
|
||
# GC_ID = gc_cell.put('g1',-dX_gc2gc/2,-dY_gc2gc/2,180)
|
||
# GC_OU = gc_cell.put('g1', dX_gc2gc/2,dY_gc2gc/2,0)
|
||
# GC_OD = gc_cell.put('g1', dX_gc2gc/2,-dY_gc2gc/2,0)
|
||
|
||
gc_ID = gc_cell.put('g1',0,-dY_gc2gc,180)
|
||
gc_IU = gc_cell.put('g1',0,0,180)
|
||
gc_OU = gc_cell.put('g1',dX_gc2gc,-dY_gc2gc,0)
|
||
gc_OD = gc_cell.put('g1',dX_gc2gc,0,0)
|
||
# Put DC
|
||
dL_DC = self.cell.pin['b1'].x - self.cell.pin['a1'].x
|
||
# inst = self.cell.put('a1',-dL_DC/2+dX_gc2gc/2,self.cell.pin['a1'].y-dY_gc2gc/2,0)
|
||
DC_pX3 = self.cell.put('a1',-dL_DC/2+dX_gc2gc/2,self.cell.pin['a1'].y-dY_gc2gc/2,0)
|
||
|
||
# Connect all the ports
|
||
stripe=Route(radius=10, width=self.w_wg, xs="strip")
|
||
stripe.sbend_p2p(pin1=gc_IU.pin['g1'],pin2=DC_pX3.pin['a1'],arrow=False,original_function=not sharp_patch).put()
|
||
stripe.sbend_p2p(pin1=gc_ID.pin['g1'],pin2=DC_pX3.pin['a2'],arrow=False,original_function=not sharp_patch).put()
|
||
stripe.sbend_p2p(pin1=gc_OD.pin['g1'],pin2=DC_pX3.pin['b1'],arrow=False,original_function=not sharp_patch).put()
|
||
stripe.sbend_p2p(pin1=gc_OU.pin['g1'],pin2=DC_pX3.pin['b2'],arrow=False,original_function=not sharp_patch).put()
|
||
return C
|
||
|