Files
mxpic_forge/mxpic/components/primitives/directional_couplers.py
T
2026-05-17 11:58:41 +08:00

1238 lines
59 KiB
Python
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
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([-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 BUScoupler 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 BUScoupler 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