1328 lines
62 KiB
Python
1328 lines
62 KiB
Python
from typing import Any, Optional
|
|
from turtle import shape
|
|
import nazca as nd
|
|
import numpy as np
|
|
import math
|
|
|
|
from .taper import taper_xs2xs
|
|
from ...routing import Route
|
|
|
|
from ...geometry import *
|
|
from ....technologies import *
|
|
|
|
from ...geometry import _my_polygon,Conchoid,_my_poly_spiral
|
|
|
|
from scipy import optimize
|
|
|
|
class spiral:
|
|
"""
|
|
spiral primitive component.
|
|
|
|
This component builds the spiral layout cell.
|
|
|
|
Parameters
|
|
----------
|
|
name : str, optional
|
|
Unique identifier for the device cell. Default is None.
|
|
shape : str, optional
|
|
Value for the shape parameter. Default is 'circle'.
|
|
Dmin : float, optional
|
|
Value for the Dmin parameter. Default is 50.
|
|
R_bend : float, optional
|
|
Radius parameter in microns. Default is 10.
|
|
Rmin_euler : float, optional
|
|
Radius parameter in microns. Default is 10.
|
|
Lmin : float, optional
|
|
Length parameter in microns. Default is 50.
|
|
width : float, optional
|
|
Width parameter in microns. Default is 2.
|
|
w_port : float, optional
|
|
Width parameter in microns. Default is 0.45.
|
|
w_bend_center : float, optional
|
|
Width parameter in microns. Default is 1.
|
|
Rmin_bend_center : float, optional
|
|
Radius parameter in microns. Default is 10.
|
|
gap : float, optional
|
|
Spacing or gap parameter in microns. Default is 1.
|
|
cycles : float, optional
|
|
Count or repetition parameter. Default is 20.
|
|
xs : str, optional
|
|
Layer or cross-section name used by the device. Default is 'strip'.
|
|
layer : str, optional
|
|
Layer or cross-section name used by the device. Default is None.
|
|
w_bend_port : Optional[float], optional
|
|
Width parameter in microns. Default is None.
|
|
Ltp_port : int, optional
|
|
Length parameter in microns. Default is 10.
|
|
res : float, optional
|
|
Value for the res parameter. Default is 0.5.
|
|
rib2strip : bool, optional
|
|
Value for the rib2strip parameter. Default is True.
|
|
port_angle : float, optional
|
|
Value for the port_angle parameter. Default is 180.
|
|
Euler_bend : bool, optional
|
|
Value for the Euler_bend parameter. Default is False.
|
|
show_pins : bool, optional
|
|
Whether to draw pin markers in the generated layout. Default is False.
|
|
sharp_patch : bool, optional
|
|
Whether to add geometry patches for sharp corners or cladding continuity. Default is True.
|
|
"""
|
|
def __init__(self,
|
|
name: str = None,
|
|
shape: str = 'circle',
|
|
Dmin: float = 50,
|
|
R_bend: float = 10,
|
|
Rmin_euler: float = 10,
|
|
Lmin: float = 50,
|
|
width: float = 2,
|
|
w_port: float = 0.45, ## not used at this moment
|
|
w_bend_center: float = 1,
|
|
Rmin_bend_center: float = 10,
|
|
gap: float = 1,
|
|
cycles: float = 20,
|
|
xs: str = 'strip',
|
|
layer: str = None,
|
|
w_bend_port: Optional[float]=None,
|
|
Ltp_port: int = 10,
|
|
|
|
# Tres: int = 256,## resolution for one cycle, these two parameters aborted in 2023.1.4
|
|
# Bres: int = 64, ## resolution for bends, these two parameters aborted in 2023.1.4
|
|
|
|
res : float = 0.5, ## added in 2023.1.4, the length resolution
|
|
rib2strip: bool=True,
|
|
|
|
port_angle: float = 180,
|
|
Euler_bend: bool = False,
|
|
show_pins: bool = False,
|
|
sharp_patch:bool = True
|
|
) -> None:
|
|
"""_summary_
|
|
|
|
Args:
|
|
name (str, optional): Name of the internal cell. Defaults to None.
|
|
shape (str, optional): circular or rectangular. Defaults to 'circle'.
|
|
Dmin (float, optional): Mimimum internal distance. Defaults to 50.
|
|
R_bend (float, optional): Radius of the bends. Defaults to 10.
|
|
Rmin_euler (float, optional): _description_. Defaults to 10.
|
|
Lmin (float, optional): Minimum length of the inner cycle. Defaults to 50.
|
|
width (float, optional): _description_. Defaults to 2.
|
|
w_port (float, optional): _description_. Defaults to 0.45.
|
|
w_bend_center (float, optional): _description_. Defaults to 1.
|
|
Rmin_bend_center (float, optional): _description_. Defaults to 10.
|
|
gap (float, optional): _description_. Defaults to 1.
|
|
cycles (float, optional): _description_. Defaults to 20.
|
|
xs (str, optional): _description_. Defaults to 'strip'.
|
|
layer (str, optional): _description_. Defaults to None.
|
|
Tres (int, optional): _description_. Defaults to 256.
|
|
port_angle (float, optional): _description_. Defaults to 90.
|
|
Euler_bend (bool, optional): _description_. Defaults to 'Euler'.
|
|
show_pins (bool, optional): _description_. Defaults to True.
|
|
sharp_patch (bool, optional): _description_. Defaults to True.
|
|
"""
|
|
|
|
|
|
self.Dmin = Dmin
|
|
self.Lmin = Lmin
|
|
self.R_bend = R_bend
|
|
self.shape = shape
|
|
self.cycles = cycles
|
|
self.width = width
|
|
self.w_port = w_port
|
|
self.gap = gap
|
|
self.xs = xs
|
|
self.layer = layer
|
|
|
|
self.name=name
|
|
if (self.name==None):
|
|
self.instantiate = False
|
|
else :
|
|
self.instantiate = True
|
|
|
|
|
|
self.port_angle=port_angle
|
|
self.w_bend_center= w_bend_center
|
|
self.Rmin_bend_center= Rmin_bend_center
|
|
self.Euler_bend= Euler_bend
|
|
self.Rmin_euler= Rmin_euler
|
|
self.sharp_patch= sharp_patch
|
|
|
|
self.w_bend_port = w_bend_port
|
|
self.Ltp_port = Ltp_port
|
|
|
|
self.rib2strip = rib2strip
|
|
|
|
self.res = res
|
|
|
|
self.cell = self.generate_gds(show_pins=show_pins)
|
|
|
|
def __strt_with_taper__(self,width1,width2,xs,length,Ltp=15,Lstart=2):
|
|
with nd.Cell(instantiate=False) as C:
|
|
|
|
L_mm = length-Ltp*2-Lstart*2
|
|
|
|
if (L_mm>0):
|
|
|
|
instr_a1 = nd.strt(length=Lstart,width=width1,xs=xs).put(0,0,0)
|
|
nd.taper(length=Ltp,width1=width1,width2=width2,xs=xs).put()
|
|
nd.strt(length=L_mm,width=width2,xs=xs).put()
|
|
nd.taper(length=Ltp,width1=width2,width2=width1,xs=xs).put()
|
|
instr_b1 = nd.strt(length=Lstart,width=width1,xs=xs).put()
|
|
|
|
else :
|
|
instr_a1 = nd.strt(length=length/2,width=width1,xs=xs).put(0,0,0)
|
|
instr_b1 = nd.strt(length=length/2,width=width1,xs=xs).put()
|
|
|
|
nd.Pin(name='a0',pin=instr_a1.pin['a0']).put()
|
|
nd.Pin(name='b0',pin=instr_b1.pin['b0']).put()
|
|
|
|
return C
|
|
|
|
def generate_gds(self,show_pins):
|
|
|
|
if (self.w_port==None):
|
|
self.w_port = self.width
|
|
if (self.w_bend_port==None):
|
|
self.w_bend_port = self.width
|
|
|
|
|
|
""" Circular Spiral """
|
|
if (self.shape=='circle'):
|
|
with nd.Cell(instantiate=self.instantiate,name=self.name) as C:
|
|
if (self.layer==None):
|
|
pitch = (self.width+self.gap)*2 ## a bi-twsited circle
|
|
|
|
Dmin = self.Dmin
|
|
R0 = Dmin/2
|
|
kR = pitch/(np.pi*2)
|
|
|
|
K_att = (np.power(R0,2)+2*np.power(kR,2))/np.power((np.power(R0,2)+np.power(kR,2)),1.5)
|
|
R_att = 1/K_att
|
|
|
|
|
|
for layers,growx,growy,acc in nd.layeriter(xs=self.xs):
|
|
(a1,b1), (a2,b2),c1,c2 = growx
|
|
|
|
""" Generating Central Euler bend """
|
|
if (self.Euler_bend == True):
|
|
""" Modified in 2023.07.31, Clothoid simplified into the simple attachment of Clothoid and Conchoid
|
|
No Angle compensation were build due to no significant improvement
|
|
"""
|
|
# spr_bend = Clothoid(xs=self.xs,R=[R_att/1.5,R_att/2.4,R_att],
|
|
spr_bend = Clothoid(xs=self.xs,R=[R_att,R_att/2.5001,R_att],
|
|
w=[self.w_bend_center,self.w_bend_port],A=[0,90,180],dL_wg=self.res,width_type='dual_sine')
|
|
|
|
else :
|
|
""" Genreating Circular bend for center """
|
|
spr_bend = Clothoid(xs=self.xs,R=[R0/2,R0/2,R0/2],
|
|
w=[self.w_bend_port,self.w_bend_port],A=[0,90,180],dL_wg=self.res,width_type='dual_sine')
|
|
|
|
w_cur = self.width*(a1-a2)+(b1-b2)
|
|
if (w_cur<pitch or self.sharp_patch==False):
|
|
|
|
Atilt = np.arctan(spr_bend.vtx_center[-1,0]/spr_bend.vtx_center[-1,1])*180/pi
|
|
Atilt = 0
|
|
self.Atilt = Atilt
|
|
# print("Distance ",np.sqrt(abs(spr_bend.vtx_center[-1,0])**2 + abs(spr_bend.vtx_center[-1,1])**2))
|
|
IN_L = spr_bend.cell.put('a0',0,0,-90-Atilt,flip=1)
|
|
IN_R = spr_bend.cell.put('a0',0,0, 90-Atilt,flip=1)
|
|
|
|
R_act = spr_bend.sz[1]*np.cos(Atilt/180*np.pi)
|
|
R_act = spr_bend.sz[1]
|
|
R_act = np.abs(IN_L.pin['b0'].x)
|
|
|
|
""" Standard part """
|
|
SPR_U = Conchoid(R0=R_act,kR=kR,T=self.cycles*pi,w=self.width*(a1-a2)+(b1-b2),
|
|
layer=layers,final_flat=None,begin_flat=None,res=self.res)
|
|
# layer=layers,final_flat=0,begin_flat=spr_bend.vtx_center[-1,0]/spr_bend.vtx_center[-1,1],res=self.res)
|
|
## revised in 2026.06.07 by Qin Yue
|
|
# legacy: SPR_U_INST = SPR_U.cell.put('a1',IN_L.pin['b0'].x,IN_L.pin['b0'].y,90,flip=1)
|
|
SPR_U_INST = SPR_U.cell.put('opt_a1',IN_L.pin['b0'].x,IN_L.pin['b0'].y,90,flip=1)
|
|
|
|
""" relative part """
|
|
SPR_D = Conchoid(R0=R_act,kR=kR,T=self.cycles*pi+pi-self.port_angle/180*np.pi,w=self.width*(a1-a2)+(b1-b2),layer=layers,
|
|
final_flat=None,
|
|
begin_flat=None,res=self.res)
|
|
# begin_flat=spr_bend.vtx_center[-1,0]/spr_bend.vtx_center[-1,1],res=self.res)
|
|
## revised in 2026.06.07 by Qin Yue
|
|
# legacy: SPR_D_INST = SPR_D.cell.put('a1',IN_R.pin['b0'].x,IN_R.pin['b0'].y,-90,flip=1)
|
|
SPR_D_INST = SPR_D.cell.put('opt_a1',IN_R.pin['b0'].x,IN_R.pin['b0'].y,-90,flip=1)
|
|
|
|
if (self.sharp_patch==True and b1!=0 and b2!=0):
|
|
sz = (R_act+pitch*self.cycles+self.port_angle/180*np.pi*kR + self.width*a1+b1)*2
|
|
nd.strt(length=sz,width=sz,layer=layers).put(-sz/2,0,0)
|
|
|
|
self.L = SPR_U.L+SPR_D.L
|
|
self.Ru = SPR_U.R_end
|
|
self.Rd = SPR_D.R_end
|
|
if (self.width!=self.w_port):
|
|
## revised in 2026.06.07 by Qin Yue
|
|
# legacy: nd.taper(xs=self.xs,width1=self.width,width2=self.w_port,length=self.Ltp_port).put(SPR_U_INST.pin['b1'])
|
|
nd.taper(xs=self.xs,width1=self.width,width2=self.w_port,length=self.Ltp_port).put(SPR_U_INST.pin['opt_b1'])
|
|
## revised in 2026.06.07 by Qin Yue
|
|
# legacy: nd.Pin(name='a1',width=self.w_port).put()
|
|
nd.Pin(name='opt_a1',width=self.w_port,type="optical:").put()
|
|
## revised in 2026.06.07 by Qin Yue
|
|
# legacy: nd.taper(xs=self.xs,width1=self.width,width2=self.w_port,length=self.Ltp_port).put(SPR_D_INST.pin['b1'])
|
|
nd.taper(xs=self.xs,width1=self.width,width2=self.w_port,length=self.Ltp_port).put(SPR_D_INST.pin['opt_b1'])
|
|
## revised in 2026.06.07 by Qin Yue
|
|
# legacy: nd.Pin(name='b1',width=self.w_port).put()
|
|
nd.Pin(name='opt_b1',width=self.w_port,type="optical:").put()
|
|
|
|
else :
|
|
## revised in 2026.06.07 by Qin Yue
|
|
# legacy: nd.Pin(name='a1',width=self.w_port).put(SPR_U_INST.pin['b1'])
|
|
nd.Pin(name='opt_a1',width=self.w_port,type="optical:").put(SPR_U_INST.pin['opt_b1'])
|
|
## revised in 2026.06.07 by Qin Yue
|
|
# legacy: nd.Pin(name='b1',width=self.width).put(SPR_D_INST.pin['b1'])
|
|
nd.Pin(name='opt_b1',width=self.width,type="optical:").put(SPR_D_INST.pin['opt_b1'])
|
|
|
|
if show_pins:
|
|
nd.put_stub()
|
|
|
|
|
|
|
|
|
|
elif (self.shape=='rectangle'):
|
|
""" Rectangluar Spiral """
|
|
with nd.Cell(instantiate=self.instantiate,name=self.name) as C:
|
|
pitch = (self.width+self.gap)*2 ## a bi-twsited circle
|
|
|
|
if (self.Euler_bend == True):
|
|
bend_rt = Clothoid(xs=self.xs,R=[self.R_bend,self.Rmin_euler,self.R_bend],
|
|
w=[self.w_bend_port,self.w_bend_center,self.w_bend_port],A=[0,45,90],dL_wg=self.res,end_patch=False,sharp_patch=self.sharp_patch)
|
|
bend_cell = bend_rt.cell
|
|
bend_sz = bend_rt.sz
|
|
L_bend = bend_rt.L0
|
|
|
|
else:
|
|
with nd.Cell(instantiate=False) as bend_cell:
|
|
inst = circle(radius=self.R_bend,width=self.width,theta_start=0,theta_stop=90,xs=self.xs,res=self.res,sharp_patch=self.sharp_patch).cell.put(0,0,0)
|
|
## revised in 2026.06.07 by Qin Yue
|
|
# legacy: nd.Pin(name='a0',pin=inst.pin['a1']).put()
|
|
nd.Pin(name='a0',pin=inst.pin['opt_a1']).put()
|
|
## revised in 2026.06.07 by Qin Yue
|
|
# legacy: nd.Pin(name='b0',pin=inst.pin['b1']).put()
|
|
nd.Pin(name='b0',pin=inst.pin['opt_b1']).put()
|
|
|
|
bend_sz = [self.R_bend,self.R_bend]
|
|
L_bend = np.pi/2*self.R_bend
|
|
|
|
self.bend_cell = bend_cell
|
|
if (self.Dmin < bend_sz[1]*2):
|
|
self.Dmin = bend_sz[1]*2
|
|
print("WARNING: In <mxpic::passive::spiral>, Dmin too small")
|
|
D_port = self.Dmin - bend_sz[1]*2
|
|
|
|
with nd.Cell(instantiate=False) as wg_mid_cell:
|
|
wg = self.__strt_with_taper__(length=self.Lmin-bend_sz[0]*2,width2=self.width,width1=bend_cell.pin['a0'].width,xs=self.xs,Ltp=self.Ltp_port).put(-self.Lmin/2+bend_sz[0]*2,0,0)
|
|
bend_cell.put(wg.pin['a0'],flip=1)
|
|
self.__strt_with_taper__(length=D_port,width2=self.width,width1=bend_cell.pin['a0'].width,xs=self.xs,Ltp=self.Ltp_port).put()
|
|
bd = bend_cell.put(flip=0)
|
|
nd.Pin(name='a0',pin=bd.pin['b0']).put()
|
|
nd.Pin(name='b0',pin=wg.pin['b0']).put()
|
|
|
|
wg_mid = wg_mid_cell.put(-self.Lmin/2,self.Dmin/2,0)
|
|
|
|
pin_U_pre = wg_mid.pin['b0']
|
|
pin_D_pre = wg_mid.pin['a0']
|
|
|
|
bend_U = bend_cell.put(pin_U_pre)
|
|
wg_U = self.__strt_with_taper__(length=pitch/2+D_port,
|
|
width2=self.width,width1=bend_U.pin['b0'].width,xs=self.xs,Ltp=self.Ltp_port).put(bend_U.pin['b0'])
|
|
bend_U2 = bend_cell.put(wg_U.pin['b0'])
|
|
wg_U = self.__strt_with_taper__(length=self.Lmin+pitch/2,
|
|
width2=self.width,width1=bend_U2.pin['b0'].width,xs=self.xs,Ltp=self.Ltp_port).put(bend_U2.pin['b0'])
|
|
pin_U_pre = wg_U.pin['b0']
|
|
|
|
bend_D = bend_cell.put(pin_D_pre)
|
|
wg_D = self.__strt_with_taper__(length=pitch/2+D_port,
|
|
width2=self.width,width1=bend_D.pin['b0'].width,xs=self.xs,Ltp=self.Ltp_port).put(bend_D.pin['b0'])
|
|
bend_D2 = bend_cell.put(wg_D.pin['b0'])
|
|
wg_D = self.__strt_with_taper__(length=self.Lmin+pitch/2,
|
|
width2=self.width,width1=bend_D2.pin['b0'].width,xs=self.xs,Ltp=self.Ltp_port).put(bend_D2.pin['b0'])
|
|
pin_D_pre = wg_D.pin['b0']
|
|
|
|
L = self.Lmin*3+pitch+L_bend
|
|
_cycle_ = 1
|
|
for _cycle_ in range(1,self.cycles-1):
|
|
bend_U = bend_cell.put(pin_U_pre)
|
|
wg_U = self.__strt_with_taper__(length=_cycle_*pitch+pitch/2+D_port,
|
|
width2=self.width,width1=bend_U.pin['b0'].width,xs=self.xs,Ltp=self.Ltp_port).put(bend_U.pin['b0'])
|
|
bend_U2 = bend_cell.put(wg_U.pin['b0'])
|
|
wg_U = self.__strt_with_taper__(length=self.Lmin+_cycle_*pitch+pitch/2,
|
|
width2=self.width,width1=bend_U2.pin['b0'].width,xs=self.xs,Ltp=self.Ltp_port).put(bend_U2.pin['b0'])
|
|
pin_U_pre = wg_U.pin['b0']
|
|
|
|
bend_D = bend_cell.put(pin_D_pre)
|
|
wg_D = self.__strt_with_taper__(length=_cycle_*pitch+pitch/2+D_port,
|
|
width2=self.width,width1=bend_D.pin['b0'].width,xs=self.xs,Ltp=self.Ltp_port).put(bend_D.pin['b0'])
|
|
bend_D2 = bend_cell.put(wg_D.pin['b0'])
|
|
wg_D = self.__strt_with_taper__(length=self.Lmin+_cycle_*pitch+pitch/2,
|
|
width2=self.width,width1=bend_D2.pin['b0'].width,xs=self.xs,Ltp=self.Ltp_port).put(bend_D2.pin['b0'])
|
|
pin_D_pre = wg_D.pin['b0']
|
|
|
|
L = L+(_cycle_*pitch-pitch/2+D_port+self.Lmin+_cycle_*pitch+pitch/2)*2+L_bend*4
|
|
|
|
|
|
""" 2023.03.19 REVISED, the spiral will end at the same Y level with begining """
|
|
bend_D = bend_cell.put(pin_D_pre)
|
|
|
|
|
|
## adding bend connection to outside
|
|
if(self.port_angle==90 or self.port_angle==270 or self.port_angle==-90):
|
|
wg_D = self.__strt_with_taper__(length=(_cycle_+1)*pitch+D_port,
|
|
width2=self.width,width1=bend_D.pin['b0'].width,xs=self.xs,Ltp=self.Ltp_port).put(bend_D.pin['b0'])
|
|
pin_D_pre = wg_D.pin['b0']
|
|
|
|
elif(self.port_angle==180) :
|
|
wg_D = self.__strt_with_taper__(length=(_cycle_+1)*pitch+D_port,
|
|
width2=self.width,width1=bend_D.pin['b0'].width,xs=self.xs,Ltp=self.Ltp_port).put(bend_D.pin['b0'])
|
|
|
|
bend_D2 = bend_cell.put(wg_D.pin['b0'],flip=1)
|
|
pin_D_pre = bend_D2.pin['b0']
|
|
elif(self.port_angle==0 or self.port_angle==360) :
|
|
|
|
wg_D = self.__strt_with_taper__(length=(_cycle_+1.5)*pitch+D_port,
|
|
width2=self.width,width1=bend_D.pin['b0'].width,xs=self.xs,Ltp=self.Ltp_port).put(bend_D.pin['b0'])
|
|
|
|
bend_D2 = bend_cell.put(wg_D.pin['b0'],flip=0)
|
|
pin_D_pre = bend_D2.pin['b0']
|
|
|
|
if (self.rib2strip):
|
|
taper = taper_xs2xs(xs_1=self.xs,xs_2='strip',w_1=self.w_bend_port,w_2=self.w_bend_port,L_taper=10,L_port=bend_sz[0]).cell.put(pin_D_pre)
|
|
pin_D_pre = taper.pin['b0']
|
|
taper = taper_xs2xs(xs_1=self.xs,xs_2='strip',w_1=self.w_bend_port,w_2=self.w_bend_port,L_taper=10,L_port=bend_sz[0]).cell.put(pin_U_pre)
|
|
pin_U_pre = taper.pin['b0']
|
|
|
|
|
|
""" 2023.03.19 REVISED, the begining will not be influenced by the rotation angle """
|
|
if (self.w_port !=self.width) :
|
|
if (self.rib2strip):
|
|
nd.taper(length=self.Ltp_port,width1=self.width,width2=self.w_port,xs='strip').put(pin_D_pre)
|
|
## revised in 2026.06.07 by Qin Yue
|
|
# legacy: nd.Pin(name='b1',width=self.w_bend_port).put()
|
|
nd.Pin(name='opt_b1',width=self.w_bend_port,type="optical:").put()
|
|
|
|
nd.taper(length=self.Ltp_port,width1=self.width,width2=self.w_port,xs='strip').put(pin_U_pre)
|
|
## revised in 2026.06.07 by Qin Yue
|
|
# legacy: nd.Pin(name='a1',width=self.w_bend_port).put()
|
|
nd.Pin(name='opt_a1',width=self.w_bend_port,type="optical:").put()
|
|
else :
|
|
nd.taper(length=self.Ltp_port,width1=self.width,width2=self.w_port,xs=self.xs).put(pin_D_pre)
|
|
## revised in 2026.06.07 by Qin Yue
|
|
# legacy: nd.Pin(name='b1',width=self.w_bend_port).put()
|
|
nd.Pin(name='opt_b1',width=self.w_bend_port,type="optical:").put()
|
|
|
|
nd.taper(length=self.Ltp_port,width1=self.width,width2=self.w_port,xs=self.xs).put(pin_U_pre)
|
|
## revised in 2026.06.07 by Qin Yue
|
|
# legacy: nd.Pin(name='a1',width=self.w_bend_port).put()
|
|
nd.Pin(name='opt_a1',width=self.w_bend_port,type="optical:").put()
|
|
else:
|
|
## revised in 2026.06.07 by Qin Yue
|
|
# legacy: nd.Pin(name='b1',width=self.w_bend_port).put(pin_D_pre)
|
|
nd.Pin(name='opt_b1',width=self.w_bend_port,type="optical:").put(pin_D_pre)
|
|
## revised in 2026.06.07 by Qin Yue
|
|
# legacy: nd.Pin(name='a1',width=self.w_bend_port).put(pin_U_pre)
|
|
nd.Pin(name='opt_a1',width=self.w_bend_port,type="optical:").put(pin_U_pre)
|
|
|
|
self.L = L
|
|
|
|
## revise 2022.08.18
|
|
if (show_pins):
|
|
nd.put_stub()
|
|
|
|
return C
|
|
|
|
class spiral_rectangle:
|
|
"""
|
|
spiral rectangle primitive component.
|
|
|
|
This component builds the spiral rectangle layout cell.
|
|
|
|
Parameters
|
|
----------
|
|
name : str, optional
|
|
Unique identifier for the device cell. Default is None.
|
|
Dmin : float, optional
|
|
Value for the Dmin parameter. Default is 50.
|
|
Rmax_bend : float, optional
|
|
Radius parameter in microns. Default is 10.
|
|
Rmin_bend : float, optional
|
|
Radius parameter in microns. Default is 10.
|
|
wmin_bend : float, optional
|
|
Value for the wmin_bend parameter. Default is 10.
|
|
Lmin : float, optional
|
|
Length parameter in microns. Default is 50.
|
|
width : float, optional
|
|
Width parameter in microns. Default is 2.
|
|
w_port : float, optional
|
|
Width parameter in microns. Default is None.
|
|
gap : float, optional
|
|
Spacing or gap parameter in microns. Default is 1.
|
|
cycles : float, optional
|
|
Count or repetition parameter. Default is 20.
|
|
xs : str, optional
|
|
Layer or cross-section name used by the device. Default is 'strip'.
|
|
layer : str, optional
|
|
Layer or cross-section name used by the device. Default is None.
|
|
w_bend_port : Optional[float], optional
|
|
Width parameter in microns. Default is None.
|
|
Lport : int, optional
|
|
Length parameter in microns. Default is 10.
|
|
Ltp : int, optional
|
|
Length parameter in microns. Default is 10.
|
|
res : float, optional
|
|
Value for the res parameter. Default is 0.5.
|
|
cell_xs_transition : Any, optional
|
|
Cell or component dependency used by this device. Default is None.
|
|
port_angle : float, optional
|
|
Value for the port_angle parameter. Default is 180.
|
|
show_pins : bool, optional
|
|
Whether to draw pin markers in the generated layout. Default is False.
|
|
sharp_patch : bool, optional
|
|
Whether to add geometry patches for sharp corners or cladding continuity. Default is True.
|
|
in_out_align : bool, optional
|
|
Value for the in_out_align parameter. Default is True.
|
|
Lpatch : float, optional
|
|
Length parameter in microns. Default is 0.05.
|
|
"""
|
|
def __init__(self,
|
|
name: str = None,
|
|
Dmin: float = 50,
|
|
Rmax_bend: float = 10,
|
|
Rmin_bend: float = 10,
|
|
wmin_bend: float = 10,
|
|
Lmin: float = 50,
|
|
width: float = 2,
|
|
w_port: float = None, ## not used at this moment
|
|
gap: float = 1,
|
|
cycles: float = 20,
|
|
xs: str = 'strip',
|
|
layer: str = None,
|
|
w_bend_port: Optional[float]=None,
|
|
Lport: int = 10,
|
|
Ltp: int = 10,
|
|
|
|
# Tres: int = 256,## resolution for one cycle, these two parameters aborted in 2023.1.4
|
|
# Bres: int = 64, ## resolution for bends, these two parameters aborted in 2023.1.4
|
|
|
|
res : float = 0.5, ## added in 2023.1.4, the length resolution
|
|
cell_xs_transition: Any=None,
|
|
|
|
port_angle: float = 180,
|
|
show_pins: bool = False,
|
|
sharp_patch:bool = True,
|
|
in_out_align: bool = True,
|
|
Lpatch: float = 0.05,
|
|
) -> None:
|
|
"""_summary_
|
|
|
|
Args:
|
|
name (str, optional): Name of the internal cell. Defaults to None.
|
|
shape (str, optional): circular or rectangular. Defaults to 'circle'.
|
|
Dmin (float, optional): Mimimum internal distance. Defaults to 50.
|
|
R_bend (float, optional): Radius of the bends. Defaults to 10.
|
|
Rmin_euler (float, optional): _description_. Defaults to 10.
|
|
Lmin (float, optional): Minimum length of the inner cycle. Defaults to 50.
|
|
width (float, optional): _description_. Defaults to 2.
|
|
w_port (float, optional): _description_. Defaults to 0.45.
|
|
w_bend_center (float, optional): _description_. Defaults to 1.
|
|
Rmin_bend_center (float, optional): _description_. Defaults to 10.
|
|
gap (float, optional): _description_. Defaults to 1.
|
|
cycles (float, optional): _description_. Defaults to 20.
|
|
xs (str, optional): _description_. Defaults to 'strip'.
|
|
layer (str, optional): _description_. Defaults to None.
|
|
Tres (int, optional): _description_. Defaults to 256.
|
|
port_angle (float, optional): _description_. Defaults to 90.
|
|
Euler_bend (bool, optional): _description_. Defaults to 'Euler'.
|
|
show_pins (bool, optional): _description_. Defaults to True.
|
|
sharp_patch (bool, optional): _description_. Defaults to True.
|
|
"""
|
|
|
|
|
|
self.Dmin = Dmin
|
|
self.Lmin = Lmin
|
|
self.Rmax_bend = Rmax_bend
|
|
self.Rmin_bend = Rmin_bend
|
|
self.wmin_bend = wmin_bend
|
|
self.cycles = cycles
|
|
self.width = width
|
|
self.w_port = w_port
|
|
self.gap = gap
|
|
self.xs = xs
|
|
self.layer = layer
|
|
|
|
self.Ltp = Ltp
|
|
self.Lpatch = Lpatch
|
|
self.name=name
|
|
if (self.name==None):
|
|
self.instantiate = False
|
|
else :
|
|
self.instantiate = True
|
|
|
|
|
|
self.port_angle=port_angle
|
|
self.sharp_patch= sharp_patch
|
|
|
|
self.w_bend_port = w_bend_port
|
|
self.Lport = Lport
|
|
|
|
if (hasattr(cell_xs_transition,'cell')):
|
|
self.cell_xs_transition = cell_xs_transition.cell
|
|
elif (isinstance(cell_xs_transition,nd.Cell)):
|
|
self.cell_xs_transition = cell_xs_transition
|
|
else :
|
|
self.cell_xs_transition = None
|
|
|
|
self.res = res
|
|
|
|
self.in_out_align = in_out_align
|
|
|
|
self.cell = self.generate_gds(show_pins=show_pins)
|
|
|
|
def __strt_with_taper__(self,width1,width2,xs,length,Ltp=15,Lstart=2):
|
|
with nd.Cell(instantiate=False) as C:
|
|
|
|
L_mm = length-Ltp*2-Lstart*2
|
|
|
|
if (L_mm>0):
|
|
|
|
instr_a1 = nd.strt(length=Lstart,width=width1,xs=xs).put(0,0,0)
|
|
nd.taper(length=Ltp,width1=width1,width2=width2,xs=xs).put()
|
|
nd.strt(length=L_mm,width=width2,xs=xs).put()
|
|
nd.taper(length=Ltp,width1=width2,width2=width1,xs=xs).put()
|
|
instr_b1 = nd.strt(length=Lstart,width=width1,xs=xs).put()
|
|
|
|
else :
|
|
instr_a1 = nd.strt(length=length/2,width=width1,xs=xs).put(0,0,0)
|
|
instr_b1 = nd.strt(length=length/2,width=width1,xs=xs).put()
|
|
|
|
nd.Pin(name='a0',pin=instr_a1.pin['a0']).put()
|
|
nd.Pin(name='b0',pin=instr_b1.pin['b0']).put()
|
|
|
|
return C
|
|
|
|
def generate_gds(self,show_pins):
|
|
|
|
if (self.w_port==None):
|
|
self.w_port = self.width
|
|
if (self.w_bend_port==None):
|
|
self.w_bend_port = self.width
|
|
|
|
""" Rectangluar Spiral """
|
|
with nd.Cell(instantiate=self.instantiate,name=self.name) as C:
|
|
pitch = (self.width+self.gap)*2 ## a bi-twsited circle
|
|
|
|
bend_rt = Clothoid(xs=self.xs,R=[self.Rmax_bend,self.Rmin_bend,self.Rmax_bend],
|
|
w=[self.w_bend_port,self.wmin_bend,self.w_bend_port],
|
|
A=[0,45,90],
|
|
dL_wg=self.res,
|
|
end_patch=False,
|
|
sharp_patch=self.sharp_patch)
|
|
|
|
bend_rt_anti = Clothoid(xs=self.xs,R=[self.Rmax_bend,self.Rmin_bend,self.Rmax_bend],
|
|
w=[self.w_bend_port,self.wmin_bend,self.w_bend_port],
|
|
A=[0,-45,-90],
|
|
dL_wg=self.res,
|
|
end_patch=False,
|
|
sharp_patch=self.sharp_patch)
|
|
|
|
""" Adding small patch to the bending connection """
|
|
with nd.Cell(instantiate=False) as bend_cell:
|
|
inst = bend_rt.cell.put(0,0,0)
|
|
nd.strt(xs=self.xs,width=self.w_bend_port,length=self.Lpatch).put(inst.pin['a0'].move(-self.Lpatch/2,0,0),flip=1)
|
|
nd.strt(xs=self.xs,width=self.w_bend_port,length=self.Lpatch).put(inst.pin['b0'].move(-self.Lpatch/2,0,0))
|
|
inst.raise_pins()
|
|
|
|
with nd.Cell(instantiate=False) as bend_cell_anti:
|
|
inst = bend_rt_anti.cell.put(0,0,0)
|
|
nd.strt(xs=self.xs,width=self.w_bend_port,length=self.Lpatch).put(inst.pin['a0'].move(-self.Lpatch/2,0,0),flip=1)
|
|
nd.strt(xs=self.xs,width=self.w_bend_port,length=self.Lpatch).put(inst.pin['b0'].move(-self.Lpatch/2,0,0))
|
|
inst.raise_pins()
|
|
|
|
# bend_cell = bend_rt.cell
|
|
# bend_cell_anti = bend_rt_anti.cell
|
|
bend_sz = bend_rt.sz
|
|
L_bend = bend_rt.L0
|
|
|
|
self.bend_cell = bend_cell
|
|
if (self.Dmin < bend_sz[1]*2):
|
|
self.Dmin = bend_sz[1]*2
|
|
print("WARNING: In <mxpic::passive::spiral>, Dmin too small")
|
|
D_port = self.Dmin - bend_sz[1]*2
|
|
|
|
with nd.Cell(instantiate=True,name="wg_mid_cell"+self.name) as wg_mid_cell:
|
|
|
|
wg = self.__strt_with_taper__(length=self.Lmin-bend_sz[0]*2-bend_sz[0],width2=self.width,width1=bend_cell.pin['a0'].
|
|
width,xs=self.xs,Ltp=self.Ltp).put(-self.Lmin/2+bend_sz[0]*2+bend_sz[0],0,0,flip=1)
|
|
bend_cell_anti.put(wg.pin['a0'],flip=0)
|
|
self.__strt_with_taper__(length=D_port,width2=self.width,width1=bend_cell.pin['a0'].width,xs=self.xs,Ltp=self.Ltp).put()
|
|
# bd = bend_cell.put(flip=0)
|
|
bd = bend_cell.put()
|
|
bd = self.__strt_with_taper__(length=bend_sz[0],
|
|
width2=bend_cell.pin['a0'].width,
|
|
width1=bend_cell.pin['a0'].width,xs=self.xs,Ltp=self.Ltp).put()
|
|
nd.Pin(name='a0',pin=bd.pin['b0']).put()
|
|
nd.Pin(name='b0',pin=wg.pin['b0']).put()
|
|
|
|
wg_mid = wg_mid_cell.put(-self.Lmin/2,self.Dmin/2,0)
|
|
L = self.Lmin-bend_sz[0]*2 + D_port + L_bend*2
|
|
|
|
pin_U_pre = wg_mid.pin['b0']
|
|
pin_D_pre = wg_mid.pin['a0']
|
|
|
|
bend_U = bend_cell_anti.put(pin_U_pre,flip=1)
|
|
wg_U = self.__strt_with_taper__(length=pitch/2+D_port,
|
|
width2=self.width,width1=bend_U.pin['b0'].width,xs=self.xs,Ltp=self.Ltp).put(bend_U.pin['b0'],flip=1)
|
|
L = L + pitch/2+D_port + L_bend
|
|
|
|
bend_U2 = bend_cell_anti.put(wg_U.pin['b0'],flip=1)
|
|
wg_U = self.__strt_with_taper__(length=self.Lmin+pitch/2,
|
|
width2=self.width,width1=bend_U2.pin['b0'].width,xs=self.xs,Ltp=self.Ltp).put(bend_U2.pin['b0'],flip=1)
|
|
pin_U_pre = wg_U.pin['b0']
|
|
L = L + self.Lmin+pitch/2 + L_bend
|
|
|
|
bend_D = bend_cell.put(pin_D_pre)
|
|
wg_D = self.__strt_with_taper__(length=pitch/2+D_port,
|
|
width2=self.width,width1=bend_D.pin['b0'].width,xs=self.xs,Ltp=self.Ltp).put(bend_D.pin['b0'])
|
|
L = L + pitch/2+D_port + L_bend
|
|
|
|
bend_D2 = bend_cell.put(wg_D.pin['b0'])
|
|
wg_D = self.__strt_with_taper__(length=self.Lmin+pitch/2,
|
|
width2=self.width,width1=bend_D2.pin['b0'].width,xs=self.xs,Ltp=self.Ltp).put(bend_D2.pin['b0'])
|
|
pin_D_pre = wg_D.pin['b0']
|
|
L = L + self.Lmin+pitch/2 + L_bend
|
|
|
|
# L = self.Lmin*3+pitch+L_bend
|
|
_cycle_ = 1
|
|
|
|
|
|
for _cycle_ in range(1,self.cycles-1):
|
|
bend_U = bend_cell_anti.put(pin_U_pre,flip=1)
|
|
wg_U = self.__strt_with_taper__(length=_cycle_*pitch+pitch/2+D_port,
|
|
width2=self.width,width1=bend_U.pin['b0'].width,xs=self.xs,Ltp=self.Ltp).put(bend_U.pin['b0'],flip=1)
|
|
|
|
L = L+_cycle_*pitch+pitch/2+D_port+L_bend
|
|
|
|
bend_U2 = bend_cell_anti.put(wg_U.pin['b0'],flip=1)
|
|
wg_U = self.__strt_with_taper__(length=self.Lmin+_cycle_*pitch+pitch/2,
|
|
width2=self.width,width1=bend_U2.pin['b0'].width,xs=self.xs,Ltp=self.Ltp).put(bend_U2.pin['b0'],flip=1)
|
|
pin_U_pre = wg_U.pin['b0']
|
|
L = L+self.Lmin+_cycle_*pitch+pitch/2+D_port+L_bend
|
|
|
|
bend_D = bend_cell.put(pin_D_pre)
|
|
wg_D = self.__strt_with_taper__(length=_cycle_*pitch+pitch/2+D_port,
|
|
width2=self.width,width1=bend_D.pin['b0'].width,xs=self.xs,Ltp=self.Ltp).put(bend_D.pin['b0'])
|
|
L = L+_cycle_*pitch+pitch/2+D_port+L_bend
|
|
|
|
bend_D2 = bend_cell.put(wg_D.pin['b0'])
|
|
wg_D = self.__strt_with_taper__(length=self.Lmin+_cycle_*pitch+pitch/2,
|
|
width2=self.width,width1=bend_D2.pin['b0'].width,xs=self.xs,Ltp=self.Ltp).put(bend_D2.pin['b0'])
|
|
pin_D_pre = wg_D.pin['b0']
|
|
L = L+self.Lmin+_cycle_*pitch+pitch/2+L_bend
|
|
|
|
|
|
""" 2023.03.19 REVISED, the spiral will end at the same Y level with begining """
|
|
|
|
## adding bend connection to outside
|
|
if(self.port_angle==90 or self.port_angle==270 or self.port_angle==-90):
|
|
|
|
bend_D = bend_cell.put(pin_D_pre)
|
|
wg_D = self.__strt_with_taper__(length=(_cycle_+1)*pitch+D_port,
|
|
width2=self.width,width1=bend_D.pin['b0'].width,xs=self.xs,Ltp=self.Ltp).put(bend_D.pin['b0'])
|
|
pin_D_pre = wg_D.pin['b0']
|
|
L = L+(_cycle_+1)*pitch+D_port+L_bend
|
|
|
|
elif(self.port_angle==180 and self.in_out_align) :
|
|
|
|
bend_D = bend_cell.put(pin_D_pre)
|
|
wg_D = self.__strt_with_taper__(length=(_cycle_+1)*pitch+D_port,
|
|
width2=self.width,width1=bend_D.pin['b0'].width,xs=self.xs,Ltp=self.Ltp).put(bend_D.pin['b0'])
|
|
|
|
bend_D2 = bend_cell_anti.put(wg_D.pin['b0'])
|
|
pin_D_pre = bend_D2.pin['b0']
|
|
L = L+(_cycle_+1)*pitch+D_port+L_bend*2
|
|
|
|
elif(self.port_angle==180 and self.in_out_align==False):
|
|
pass
|
|
|
|
elif(self.port_angle==0 or self.port_angle==360) :
|
|
|
|
bend_D = bend_cell.put(pin_D_pre)
|
|
wg_D = self.__strt_with_taper__(length=(_cycle_+1.5)*pitch+D_port,
|
|
width2=self.width,width1=bend_D.pin['b0'].width,xs=self.xs,Ltp=self.Ltp).put(bend_D.pin['b0'])
|
|
|
|
bend_D2 = bend_cell.put(wg_D.pin['b0'],flip=0)
|
|
pin_D_pre = bend_D2.pin['b0']
|
|
L = L+(_cycle_+1.5)*pitch+D_port+L_bend*2
|
|
|
|
|
|
|
|
|
|
|
|
""" 2023.03.19 REVISED, the begining will not be influenced by the rotation angle """
|
|
""" 2023.09.18 REVISED, the xsection output can be defined with the transition area """
|
|
if (self.w_port !=self.width) :
|
|
pin_D_pre = nd.taper(length=self.Lport,width1=pin_D_pre.width,width2=self.w_port,xs=self.xs).put(pin_D_pre)
|
|
pin_U_pre = nd.taper(length=self.Lport,width1=pin_U_pre.width,width2=self.w_port,xs=self.xs).put(pin_U_pre)
|
|
|
|
|
|
""" 2023.09.18 REVISED, the transition area are only added at w_port area """
|
|
### Because putting transisiton in multimode area are dengerous
|
|
|
|
if (self.cell_xs_transition != None):
|
|
|
|
taper = self.cell_xs_transition.put(pin_D_pre)
|
|
pin_D_pre = taper.pin['b0']
|
|
taper = self.cell_xs_transition.put(pin_U_pre)
|
|
pin_U_pre = taper.pin['b0']
|
|
|
|
## revised in 2026.06.07 by Qin Yue
|
|
# legacy: nd.Pin(name='b1',width=self.w_port).put(pin_D_pre)
|
|
nd.Pin(name='opt_b1',width=self.w_port,type="optical:").put(pin_D_pre)
|
|
## revised in 2026.06.07 by Qin Yue
|
|
# legacy: nd.Pin(name='a1',width=self.w_port).put(pin_U_pre)
|
|
nd.Pin(name='opt_a1',width=self.w_port,type="optical:").put(pin_U_pre)
|
|
|
|
self.L = L
|
|
|
|
## revise 2022.08.18
|
|
if (show_pins):
|
|
nd.put_stub()
|
|
|
|
return C
|
|
|
|
class Spiral_Rect_STD(spiral_rectangle):
|
|
"""
|
|
Spiral Rect STD primitive component.
|
|
|
|
This component builds the Spiral Rect STD layout cell.
|
|
|
|
Parameters
|
|
----------
|
|
name : str, optional
|
|
Unique identifier for the device cell. Default is None.
|
|
Dmin : float, optional
|
|
Value for the Dmin parameter. Default is 50.
|
|
R_bend : float, optional
|
|
Radius parameter in microns. Default is 10.
|
|
Lmin : float, optional
|
|
Length parameter in microns. Default is 50.
|
|
width : float, optional
|
|
Width parameter in microns. Default is 2.
|
|
w_port : float, optional
|
|
Width parameter in microns. Default is 0.45.
|
|
gap : float, optional
|
|
Spacing or gap parameter in microns. Default is 1.
|
|
cycles : float, optional
|
|
Count or repetition parameter. Default is 20.
|
|
xs : str, optional
|
|
Layer or cross-section name used by the device. Default is 'strip'.
|
|
layer : str, optional
|
|
Layer or cross-section name used by the device. Default is None.
|
|
Lport : int, optional
|
|
Length parameter in microns. Default is 10.
|
|
in_out_align : bool, optional
|
|
Value for the in_out_align parameter. Default is True.
|
|
res : float, optional
|
|
Value for the res parameter. Default is 0.5.
|
|
cell_xs_transition : Any, optional
|
|
Cell or component dependency used by this device. Default is None.
|
|
port_angle : float, optional
|
|
Value for the port_angle parameter. Default is 180.
|
|
show_pins : bool, optional
|
|
Whether to draw pin markers in the generated layout. Default is False.
|
|
sharp_patch : bool, optional
|
|
Whether to add geometry patches for sharp corners or cladding continuity. Default is True.
|
|
"""
|
|
def __init__(self,
|
|
name: str = None,
|
|
Dmin: float = 50,
|
|
R_bend: float = 10,
|
|
Lmin: float = 50,
|
|
width: float = 2,
|
|
w_port: float = 0.45,
|
|
gap: float = 1,
|
|
cycles: float = 20,
|
|
xs: str = 'strip',
|
|
layer: str = None,
|
|
Lport: int=10,
|
|
in_out_align: bool = True,
|
|
res: float = 0.5,
|
|
cell_xs_transition: Any=None,
|
|
port_angle: float = 180,
|
|
show_pins: bool = False,
|
|
sharp_patch: bool = True) -> None:
|
|
super().__init__(name=name,
|
|
Dmin=Dmin,
|
|
Rmax_bend=R_bend,
|
|
Rmin_bend=R_bend,
|
|
wmin_bend=width,
|
|
Lmin=Lmin,
|
|
width=width,
|
|
w_port=w_port,
|
|
gap=gap,
|
|
cycles=cycles,
|
|
xs=xs,
|
|
layer=layer,
|
|
w_bend_port=None,
|
|
Lport=Lport,
|
|
res=res,
|
|
cell_xs_transition=cell_xs_transition,
|
|
port_angle=port_angle,
|
|
show_pins=show_pins,
|
|
sharp_patch=sharp_patch,
|
|
in_out_align=in_out_align)
|
|
|
|
class spiral_circle:
|
|
"""
|
|
spiral circle primitive component.
|
|
|
|
This component builds the spiral circle layout cell.
|
|
|
|
Parameters
|
|
----------
|
|
name : str, optional
|
|
Unique identifier for the device cell. Default is None.
|
|
Dmin : float, optional
|
|
Value for the Dmin parameter. Default is 50.
|
|
width : float, optional
|
|
Width parameter in microns. Default is 2.
|
|
w_port : float, optional
|
|
Width parameter in microns. Default is 0.45.
|
|
w_bend_center : float, optional
|
|
Width parameter in microns. Default is 1.
|
|
gap : float, optional
|
|
Spacing or gap parameter in microns. Default is 1.
|
|
cycles : float, optional
|
|
Count or repetition parameter. Default is 20.
|
|
xs : str, optional
|
|
Layer or cross-section name used by the device. Default is 'strip'.
|
|
layer : str, optional
|
|
Layer or cross-section name used by the device. Default is None.
|
|
Lport : int, optional
|
|
Length parameter in microns. Default is 10.
|
|
res : float, optional
|
|
Value for the res parameter. Default is 0.5.
|
|
rib2strip : bool, optional
|
|
Value for the rib2strip parameter. Default is True.
|
|
port_angle : float, optional
|
|
Value for the port_angle parameter. Default is 180.
|
|
Euler_Sbend : bool, optional
|
|
Value for the Euler_Sbend parameter. Default is False.
|
|
show_pins : bool, optional
|
|
Whether to draw pin markers in the generated layout. Default is False.
|
|
sharp_patch : bool, optional
|
|
Whether to add geometry patches for sharp corners or cladding continuity. Default is True.
|
|
strict_condition : bool, optional
|
|
Value for the strict_condition parameter. Default is False.
|
|
R_ratio_mamnual : Any, optional
|
|
Radius parameter in microns. Default is None.
|
|
"""
|
|
def __init__(self,
|
|
name: str = None,
|
|
Dmin: float = 50,
|
|
width: float = 2,
|
|
w_port: float = 0.45, ## not used at this moment
|
|
w_bend_center: float = 1,
|
|
gap: float = 1,
|
|
cycles: float = 20,
|
|
xs: str = 'strip',
|
|
layer: str = None,
|
|
Lport: int = 10,
|
|
|
|
res : float = 0.5, ## added in 2023.1.4, the length resolution
|
|
rib2strip: bool=True,
|
|
|
|
port_angle: float = 180,
|
|
Euler_Sbend: bool = False,
|
|
show_pins: bool = False,
|
|
sharp_patch:bool = True,
|
|
strict_condition: bool = False,
|
|
R_ratio_mamnual: Any = None,
|
|
) -> None:
|
|
"""_summary_
|
|
|
|
Args:
|
|
name (str, optional): Name of the internal cell. Defaults to None.
|
|
Dmin (float, optional): Mimimum internal distance. Defaults to 50.
|
|
width (float, optional): _description_. Defaults to 2.
|
|
w_port (float, optional): _description_. Defaults to 0.45.
|
|
w_bend_center (float, optional): _description_. Defaults to 1.
|
|
gap (float, optional): _description_. Defaults to 1.
|
|
cycles (float, optional): _description_. Defaults to 20.
|
|
xs (str, optional): _description_. Defaults to 'strip'.
|
|
layer (str, optional): _description_. Defaults to None.
|
|
Tres (int, optional): _description_. Defaults to 256.
|
|
port_angle (float, optional): _description_. Defaults to 90.
|
|
Euler_bend (bool, optional): _description_. Defaults to 'Euler'.
|
|
show_pins (bool, optional): _description_. Defaults to True.
|
|
sharp_patch (bool, optional): _description_. Defaults to True.
|
|
"""
|
|
|
|
|
|
self.Dmin = Dmin
|
|
self.cycles = cycles
|
|
self.width = width
|
|
self.w_port = w_port
|
|
self.gap = gap
|
|
self.xs = xs
|
|
self.layer = layer
|
|
|
|
self.name=name
|
|
if (self.name==None):
|
|
self.instantiate = False
|
|
else :
|
|
self.instantiate = True
|
|
|
|
|
|
self.port_angle=port_angle
|
|
self.w_bend_center= w_bend_center
|
|
self.Euler_Sbend= Euler_Sbend
|
|
self.sharp_patch= sharp_patch
|
|
|
|
self.Lport = Lport
|
|
|
|
self.rib2strip = rib2strip
|
|
|
|
self.res = res
|
|
self.strict_condition = strict_condition
|
|
|
|
self.R_ratio_mamnual = R_ratio_mamnual
|
|
|
|
self.cell = self.generate_gds(show_pins=show_pins)
|
|
|
|
""" Optimizing the bend radius for the minimum and central """
|
|
def opt_euler(self,R,R0):
|
|
(R1,R2) = R
|
|
R1 = R1
|
|
R2 = R2
|
|
[A,LA] = _my_poly_spiral(r=[R0/R1,R0/R2],theta=[0,90],res=0.01,R_max=1000,order=1)
|
|
[B,LA] = _my_poly_spiral(r=[R0/R2,R0],theta=[90,180],res=0.01,R_max=1000,order=1)
|
|
|
|
x_final = A[-1,0]+B[-1,0]
|
|
y_final = A[-1,1]+B[-1,1]
|
|
|
|
D_final = np.sqrt(x_final**2 + y_final**2)
|
|
A_final = abs(np.arctan(x_final/y_final))*180/pi
|
|
|
|
# Dmis = abs(D_final-D0)
|
|
# Amis = abs(abs(A_final)-abs(A0))
|
|
|
|
return [D_final,A_final]
|
|
|
|
|
|
def generate_gds(self,show_pins):
|
|
|
|
if (self.w_port==None):
|
|
self.w_port = self.width
|
|
|
|
with nd.Cell(instantiate=self.instantiate,name=self.name) as C:
|
|
if (self.layer==None):
|
|
pitch = (self.width+self.gap)*2 ## a bi-twsited circle
|
|
|
|
Dmin = self.Dmin
|
|
R0 = Dmin/2
|
|
kR = pitch/(np.pi*2)
|
|
|
|
K_att = (np.power(R0,2)+2*np.power(kR,2))/np.power((np.power(R0,2)+np.power(kR,2)),1.5)
|
|
R_att = 1/K_att
|
|
|
|
Atilt_con = abs(np.arctan(kR/R0))*180/pi ## the initial tilt norm of the conchoid
|
|
|
|
""" Calculating the spiral with internal Sbend """
|
|
if (self.Euler_Sbend):
|
|
|
|
if (self.R_ratio_mamnual==None):
|
|
n_swp = 201
|
|
R1_range = np.linspace(0.9,1.4,n_swp)
|
|
R2_range = np.linspace(2.4,2.6,n_swp)
|
|
mis = np.zeros((n_swp,n_swp))
|
|
|
|
for ix in range(0,len(R1_range)):
|
|
for iy in range(0,len(R2_range)):
|
|
temp = self.opt_euler((R1_range[ix],R2_range[iy]),R0=R_att)
|
|
mis[ix,iy] = abs(temp[0]-R0)+abs(temp[1]-abs(Atilt_con))*10
|
|
|
|
idx = mis.argmin()
|
|
|
|
ix = idx//n_swp
|
|
iy = np.mod(idx,n_swp)
|
|
|
|
Rc_ratio = R1_range[ix]
|
|
Rm_ratio = R2_range[iy]
|
|
final_mismatch = self.opt_euler((Rc_ratio,Rm_ratio),R_att)
|
|
|
|
print("====================================================")
|
|
print("Optimized D/A = %.3f -- %.3f" % (final_mismatch[0],final_mismatch[1]))
|
|
print("Target D/A = %.3f -- %.3f" % (R0,Atilt_con))
|
|
print("Optimized para = %.3f -- %.3f" % (Rc_ratio,Rm_ratio))
|
|
|
|
else :
|
|
Rc_ratio = self.R_ratio_mamnual[0]
|
|
Rm_ratio = self.R_ratio_mamnual[1]
|
|
print("====================================================")
|
|
print("manual para = %.3f -- %.3f" % (Rc_ratio,Rm_ratio))
|
|
|
|
|
|
for layers,growx,growy,acc in nd.layeriter(xs=self.xs):
|
|
(a1,b1), (a2,b2),c1,c2 = growx
|
|
""" Generating Central Euler bend """
|
|
if (self.Euler_Sbend == True):
|
|
spr_bend = Clothoid(xs=self.xs,R=[R_att/Rc_ratio,R_att/Rm_ratio,R_att],
|
|
w=[self.w_bend_center,self.width],A=[0,90,180],dL_wg=self.res,
|
|
width_type='dual_sine',dL_cal=0.01)
|
|
|
|
else :
|
|
""" Genreating Circular bend for center """
|
|
spr_bend = Clothoid(xs=self.xs,R=[R0/2,R0/2,R0/2],
|
|
w=[self.width,self.width],A=[0,90,180],dL_wg=self.res,
|
|
width_type='dual_sine',dL_cal=0.01)
|
|
|
|
w_cur = self.width*(a1-a2)+(b1-b2)
|
|
|
|
if (w_cur<pitch or self.sharp_patch==False):
|
|
if (self.R_ratio_mamnual==None):
|
|
Atilt = np.arctan(spr_bend.vtx_center[-1,0]/spr_bend.vtx_center[-1,1])*180/pi
|
|
else:
|
|
if (self.R_ratio_mamnual[2]==None):
|
|
Atilt = np.arctan(spr_bend.vtx_center[-1,0]/spr_bend.vtx_center[-1,1])*180/pi
|
|
else :
|
|
Atilt = self.R_ratio_mamnual[2]
|
|
|
|
self.Atilt = Atilt
|
|
|
|
|
|
D0 = np.sqrt(abs(spr_bend.vtx_center[-1,0])**2 + abs(spr_bend.vtx_center[-1,1])**2)
|
|
|
|
Arot = self.cycles*180
|
|
|
|
IN_L = spr_bend.cell.put('a0',0,0,-90-Atilt + Arot,flip=1)
|
|
IN_R = spr_bend.cell.put('a0',0,0, 90-Atilt + Arot,flip=1)
|
|
|
|
# R_act = spr_bend.sz[1]*np.cos(Atilt/180*np.pi)
|
|
# R_act = spr_bend.sz[1]
|
|
R_act = np.sqrt(np.abs(IN_L.pin['b0'].x**2 + IN_L.pin['b0'].y**2))
|
|
|
|
if (self.strict_condition==False):
|
|
kR = np.tan(Atilt_con/180*np.pi)*R_act
|
|
pitch_actual = kR*2*np.pi
|
|
gap_actual = (pitch_actual - self.width*2)/2
|
|
|
|
# print("Actual Clothoid gap: %.3f" % (gap_actual))
|
|
|
|
|
|
""" Standard part """
|
|
SPR_U = Conchoid(R0=R_act,kR=kR,T=self.cycles*pi,w=self.width*(a1-a2)+(b1-b2),w_end=self.w_port,
|
|
layer=layers,
|
|
final_flat=180*self.cycles,
|
|
begin_flat=self.Atilt,
|
|
res=self.res)
|
|
# if (self.Euler_Sbend):
|
|
# print("Actual D/A = %.3f -- %.3f" % (D0,self.Atilt))
|
|
# print("Actual D/A Clothoid %.3f -- %.3f" % (R_act,SPR_U.Atilt))
|
|
# print("")
|
|
|
|
|
|
## revised in 2026.06.07 by Qin Yue
|
|
# legacy: SPR_U_INST = SPR_U.cell.put('a1',IN_L.pin['b0'].x,IN_L.pin['b0'].y,90 + Arot,flip=1)
|
|
SPR_U_INST = SPR_U.cell.put('opt_a1',IN_L.pin['b0'].x,IN_L.pin['b0'].y,90 + Arot,flip=1)
|
|
w_cur = self.width*(a1-a2)+(b1-b2)
|
|
|
|
## adding connection patch
|
|
nd.taper(layer=layers,width1=w_cur,width2=w_cur-0.01,length=0.01).put(IN_L.pin['b0'].x,IN_L.pin['b0'].y,-90-self.Atilt+Arot)
|
|
nd.taper(layer=layers,width1=w_cur,width2=w_cur-0.01,length=0.01).put(IN_L.pin['b0'].x,IN_L.pin['b0'].y, 90-self.Atilt+Arot)
|
|
|
|
""" relative part """
|
|
SPR_D = Conchoid(R0=R_act,kR=kR,T=self.cycles*pi+pi-self.port_angle/180*np.pi,w=self.width*(a1-a2)+(b1-b2),layer=layers,
|
|
final_flat=180*self.cycles+180-self.port_angle,w_end=self.w_port,
|
|
begin_flat=self.Atilt,
|
|
|
|
res=self.res)
|
|
|
|
## revised in 2026.06.07 by Qin Yue
|
|
# legacy: SPR_D_INST = SPR_D.cell.put('a1',IN_R.pin['b0'].x,IN_R.pin['b0'].y,-90 + Arot,flip=1)
|
|
SPR_D_INST = SPR_D.cell.put('opt_a1',IN_R.pin['b0'].x,IN_R.pin['b0'].y,-90 + Arot,flip=1)
|
|
nd.taper(layer=layers,width1=w_cur,width2=w_cur-0.01,length=0.01).put(IN_R.pin['b0'].x,IN_R.pin['b0'].y,-90-self.Atilt+Arot)
|
|
nd.taper(layer=layers,width1=w_cur,width2=w_cur-0.01,length=0.01).put(IN_R.pin['b0'].x,IN_R.pin['b0'].y, 90-self.Atilt+Arot)
|
|
|
|
## revised in 2026.06.07 by Qin Yue
|
|
# legacy: self.Rmax = np.sqrt(SPR_D_INST.pin['b1'].x**2 + SPR_D_INST.pin['b1'].y**2)
|
|
self.Rmax = np.sqrt(SPR_D_INST.pin['opt_b1'].x**2 + SPR_D_INST.pin['opt_b1'].y**2)
|
|
|
|
if (self.sharp_patch==True and b1!=0 and b2!=0):
|
|
sz = (R_act+pitch*self.cycles+self.port_angle/180*np.pi*kR + self.width*a1+b1)*2
|
|
nd.strt(length=sz,width=sz,layer=layers).put(-sz/2,0,0)
|
|
|
|
self.L = SPR_U.L+SPR_D.L
|
|
self.Ru = SPR_U.R_end
|
|
self.Rd = SPR_D.R_end
|
|
|
|
## revised in 2026.06.07 by Qin Yue
|
|
# legacy: nd.taper(xs=self.xs,width1=self.w_port,width2=self.w_port,length=self.Lport).put(SPR_U_INST.pin['b1'])
|
|
nd.taper(xs=self.xs,width1=self.w_port,width2=self.w_port,length=self.Lport).put(SPR_U_INST.pin['opt_b1'])
|
|
## revised in 2026.06.07 by Qin Yue
|
|
# legacy: nd.Pin(name='a1',width=self.w_port).put()
|
|
nd.Pin(name='opt_a1',width=self.w_port,type="optical:").put()
|
|
## revised in 2026.06.07 by Qin Yue
|
|
# legacy: nd.taper(xs=self.xs,width1=self.w_port,width2=self.w_port,length=self.Lport).put(SPR_D_INST.pin['b1'])
|
|
nd.taper(xs=self.xs,width1=self.w_port,width2=self.w_port,length=self.Lport).put(SPR_D_INST.pin['opt_b1'])
|
|
## revised in 2026.06.07 by Qin Yue
|
|
# legacy: nd.Pin(name='b1',width=self.w_port).put()
|
|
nd.Pin(name='opt_b1',width=self.w_port,type="optical:").put()
|
|
|
|
if show_pins:
|
|
nd.put_stub()
|
|
|
|
|
|
|
|
|
|
|
|
|
|
return C
|
|
|
|
class Spiral_Cicle_MM(spiral_circle):
|
|
"""
|
|
Spiral Cicle MM primitive component.
|
|
|
|
This component builds the Spiral Cicle MM layout cell.
|
|
|
|
Parameters
|
|
----------
|
|
name : str, optional
|
|
Unique identifier for the device cell. Default is None.
|
|
Dmin : float, optional
|
|
Value for the Dmin parameter. Default is 50.
|
|
width : float, optional
|
|
Width parameter in microns. Default is 2.
|
|
w_port : float, optional
|
|
Width parameter in microns. Default is 0.45.
|
|
w_bend_center : float, optional
|
|
Width parameter in microns. Default is 1.
|
|
gap : float, optional
|
|
Spacing or gap parameter in microns. Default is 1.
|
|
cycles : float, optional
|
|
Count or repetition parameter. Default is 20.
|
|
xs : str, optional
|
|
Layer or cross-section name used by the device. Default is 'strip'.
|
|
layer : str, optional
|
|
Layer or cross-section name used by the device. Default is None.
|
|
Lport : int, optional
|
|
Length parameter in microns. Default is 10.
|
|
res : float, optional
|
|
Value for the res parameter. Default is 0.5.
|
|
rib2strip : bool, optional
|
|
Value for the rib2strip parameter. Default is True.
|
|
port_angle : float, optional
|
|
Value for the port_angle parameter. Default is 180.
|
|
show_pins : bool, optional
|
|
Whether to draw pin markers in the generated layout. Default is False.
|
|
sharp_patch : bool, optional
|
|
Whether to add geometry patches for sharp corners or cladding continuity. Default is True.
|
|
strict_condition : bool, optional
|
|
Value for the strict_condition parameter. Default is False.
|
|
"""
|
|
def __init__(self,
|
|
name: str = None,
|
|
Dmin: float = 50,
|
|
width: float = 2,
|
|
w_port: float = 0.45,
|
|
w_bend_center: float = 1,
|
|
gap: float = 1,
|
|
cycles: float = 20,
|
|
xs: str = 'strip',
|
|
layer: str = None,
|
|
Lport: int=10,
|
|
res: float = 0.5,
|
|
rib2strip: bool=True,
|
|
port_angle: float = 180,
|
|
show_pins: bool = False,
|
|
sharp_patch: bool = True,
|
|
strict_condition: bool=False) -> None:
|
|
super().__init__(name,
|
|
Dmin,
|
|
width,
|
|
w_port,
|
|
w_bend_center,
|
|
gap, cycles,
|
|
xs, layer,
|
|
Lport, res,
|
|
rib2strip, port_angle,
|
|
Euler_Sbend=True,
|
|
show_pins=show_pins,
|
|
sharp_patch=sharp_patch,
|
|
strict_condition=strict_condition)
|
|
|
|
class Spiral_Cicle_STD(spiral_circle):
|
|
"""
|
|
Spiral Cicle STD primitive component.
|
|
|
|
This component builds the Spiral Cicle STD layout cell.
|
|
|
|
Parameters
|
|
----------
|
|
name : str, optional
|
|
Unique identifier for the device cell. Default is None.
|
|
Dmin : float, optional
|
|
Value for the Dmin parameter. Default is 50.
|
|
width : float, optional
|
|
Width parameter in microns. Default is 2.
|
|
w_port : float, optional
|
|
Width parameter in microns. Default is 0.45.
|
|
gap : float, optional
|
|
Spacing or gap parameter in microns. Default is 1.
|
|
cycles : float, optional
|
|
Count or repetition parameter. Default is 20.
|
|
xs : str, optional
|
|
Layer or cross-section name used by the device. Default is 'strip'.
|
|
layer : str, optional
|
|
Layer or cross-section name used by the device. Default is None.
|
|
Lport : int, optional
|
|
Length parameter in microns. Default is 10.
|
|
res : float, optional
|
|
Value for the res parameter. Default is 0.5.
|
|
rib2strip : bool, optional
|
|
Value for the rib2strip parameter. Default is True.
|
|
port_angle : float, optional
|
|
Value for the port_angle parameter. Default is 180.
|
|
show_pins : bool, optional
|
|
Whether to draw pin markers in the generated layout. Default is False.
|
|
sharp_patch : bool, optional
|
|
Whether to add geometry patches for sharp corners or cladding continuity. Default is True.
|
|
strict_condition : bool, optional
|
|
Value for the strict_condition parameter. Default is False.
|
|
"""
|
|
def __init__(self,
|
|
name: str = None,
|
|
Dmin: float = 50,
|
|
width: float = 2,
|
|
w_port: float = 0.45,
|
|
gap: float = 1,
|
|
cycles: float = 20,
|
|
xs: str = 'strip',
|
|
layer: str = None,
|
|
Lport: int=10,
|
|
res: float = 0.5,
|
|
rib2strip: bool=True,
|
|
port_angle: float = 180,
|
|
show_pins: bool = False,
|
|
sharp_patch: bool = True,
|
|
strict_condition: bool=False) -> None:
|
|
super().__init__(name,
|
|
Dmin,
|
|
width,
|
|
w_port,
|
|
w_bend_center=width,
|
|
gap=gap,
|
|
cycles=cycles,
|
|
xs=xs,
|
|
layer=layer,
|
|
Lport=Lport,
|
|
res=res,
|
|
rib2strip=rib2strip,
|
|
port_angle=port_angle,
|
|
Euler_Sbend=False,
|
|
show_pins=show_pins,
|
|
sharp_patch=sharp_patch,
|
|
strict_condition=strict_condition)
|