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

1193 lines
54 KiB
Python

from turtle import shape
import nazca as nd
import numpy as np
import math
from ...routing import Route
from ...structures import *
from ...foundries import *
from ...structures import _my_polygon,Conchoid,_my_poly_spiral
from scipy import optimize
class spiral:
"""
Parametric waveguide spiral supporting circular or rectangular footprints.
Parameters
----------
name : str, optional
Nazca cell name (default is None).
shape : str, optional
Footprint style used for the spiral path, circular or rectangular (default is "circle").
Dmin : float, optional
Minimum inner diameter in microns; sets the first loop radius (default is 50).
R_bend : float, optional
Bend radius in microns for rectangular implementations (default is 10).
Rmin_euler : float, optional
Minimum radius inside Euler bends when ``Euler_bend`` is True (default is 10).
Lmin : float, optional
Straight length in microns used by the innermost rectangular loop (default is 50).
width : float, optional
Nominal waveguide width inside the spiral body (default is 2).
w_port : float, optional
Output-port width in microns. ``None`` inherits ``width`` (default is 0.45).
w_bend_center : float, optional
Waveguide width used in the central attachment bends (default is 1).
Rmin_bend_center : float, optional
Minimum radius for the attachment bends (default is 10).
gap : float, optional
Spacing between adjacent turns in microns (default is 1).
cycles : float, optional
Number of half-turns (π radians) laid out in the spiral (default is 20).
xs : str, optional
Cross-section key for the entire structure (default is "strip").
layer : str, optional
Override layer for polygons; ``None`` derives from ``xs`` (default is None).
w_bend_port : float or None, optional
Width inside the outermost bends; ``None`` inherits ``width`` (default is None).
Ltp_port : float, optional
Length of straight tapers that adapt ``width`` to ``w_port`` (default is 10).
res : float, optional
Arc-length sampling step (µm) used for polygon tessellation (default is 0.5).
cell_transition : nazca.Cell, optional
Insert XS transitions cell to strip when connecting ports (default is None).
port_angle : float, optional
Output-port deflection angle in degrees, measured from +x (default is 180).
Euler_bend : bool, optional
Use Euler/Clothoid bends at the center instead of circular bends (default is False).
show_pins : bool, optional
Draw Nazca stub markers when True (default is False).
sharp_patch : bool, optional
Add chamfer helper polygons when True (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=None,
Ltp_port = 10,
res : float = 0.5, ## added in 2023.1.4, the length resolution
cell_transition : nd.Cell = None,
port_angle: float = 180,
Euler_bend: bool = False,
show_pins: bool = False,
sharp_patch:bool = True,
sample_build:bool = False,
):
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.cell_transition = cell_transition
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)
SPR_U_INST = SPR_U.cell.put('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)
SPR_D_INST = SPR_D.cell.put('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):
nd.taper(xs=self.xs,width1=self.width,width2=self.w_port,length=self.Ltp_port).put(SPR_U_INST.pin['b1'])
nd.Pin(name='a1',width=self.w_port).put()
nd.taper(xs=self.xs,width1=self.width,width2=self.w_port,length=self.Ltp_port).put(SPR_D_INST.pin['b1'])
nd.Pin(name='b1',width=self.w_port).put()
else :
nd.Pin(name='a1',width=self.w_port).put(SPR_U_INST.pin['b1'])
nd.Pin(name='b1',width=self.width).put(SPR_D_INST.pin['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)
nd.Pin(name='a0',pin=inst.pin['a1']).put()
nd.Pin(name='b0',pin=inst.pin['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.cell_transition is not None):
taper = self.cell_transition.put(pin_D_pre)
pin_D_pre = taper.pin['b0']
taper = self.cell_transition.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)
nd.Pin(name='b1',width=self.w_bend_port).put()
nd.taper(length=self.Ltp_port,width1=self.width,width2=self.w_port,xs='strip').put(pin_U_pre)
nd.Pin(name='a1',width=self.w_bend_port).put()
else :
nd.taper(length=self.Ltp_port,width1=self.width,width2=self.w_port,xs=self.xs).put(pin_D_pre)
nd.Pin(name='b1',width=self.w_bend_port).put()
nd.taper(length=self.Ltp_port,width1=self.width,width2=self.w_port,xs=self.xs).put(pin_U_pre)
nd.Pin(name='a1',width=self.w_bend_port).put()
else:
nd.Pin(name='b1',width=self.w_bend_port).put(pin_D_pre)
nd.Pin(name='a1',width=self.w_bend_port).put(pin_U_pre)
self.L = L
## revise 2022.08.18
if (show_pins):
nd.put_stub()
return C
class spiral_rectangle:
"""
Rectangular spiral with optional cross-section transitions and alignment control.
Parameters
----------
name : str or None, optional
Nazca cell name (default is None).
Dmin : float, optional
Minimum vertical separation between the first pair of bends (default is 50).
Rmax_bend : float, optional
Maximum radius used inside Clothoid bends (default is 10).
Rmin_bend : float, optional
Minimum radius reached inside Clothoid bends (default is 10).
wmin_bend : float, optional
Minimum waveguide width inside bends (default is 10).
Lmin : float, optional
Straight length of the innermost segment (default is 50).
width : float, optional
Nominal waveguide width along the spiral (default is 2).
w_port : float, optional
IO waveguide width after the final taper (default is 0.45).
gap : float, optional
Spacing between successive turns (default is 1).
cycles : float, optional
Number of rectangular loops (default is 20).
xs : str, optional
Cross-section key (default is "strip").
layer : str, optional
Override polygon layer (default is None).
w_bend_port : float or None, optional
Bend waveguide width; inherits ``width`` when None (default is None).
Lport : float, optional
Length of straight sections appended at each port (default is 10).
Ltp : float, optional
Taper length that converts bend width to ``width`` (default is 10).
res : float, optional
Arc-length sampling resolution (default is 0.5).
cell_xs_transition : nd.Cell or object, optional
Pre-built cell that performs cross-section transitions after the ports (default is None).
port_angle : float, optional
Output bend angle in degrees (default is 180).
show_pins : bool, optional
Draw Nazca stub markers when True (default is False).
sharp_patch : bool, optional
Insert chamfer helpers when True (default is True).
in_out_align : bool, optional
If True, align input/output along the same axis when ``port_angle=180`` (default is True).
Lpatch : float, optional
Small straight length inserted before/after bends to ease Boolean ops (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 = 0.45, ## not used at this moment
gap: float = 1,
cycles: float = 20,
xs: str = 'strip',
layer: str = None,
w_bend_port=None,
Lport = 10,
Ltp = 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=None,
port_angle: float = 180,
show_pins: bool = False,
sharp_patch:bool = True,
in_out_align = True,
Lpatch = 0.05,
sample_build:bool = False,
):
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
if (self.name is None):
wg_mid_name = None
else:
wg_mid_name = "wg_mid_cell"+self.name
with nd.Cell(instantiate=self.instantiate,name=wg_mid_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=self.w_port,width2=self.w_port,xs=self.xs).put(pin_D_pre)
pin_U_pre = nd.taper(length=self.Lport,width1=self.w_port,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']
nd.Pin(name='b1',width=self.w_port).put(pin_D_pre)
nd.Pin(name='a1',width=self.w_port).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):
"""
Convenience preset for rectangular spirals that share a single bend radius.
Parameters
----------
name : str or None, optional
Nazca cell name (default is None).
Dmin : float, optional
Minimum inner spacing between the first pair of bends (default is 50).
R_bend : float, optional
Bend radius applied to every corner (default is 10).
Lmin : float, optional
Straight length of the innermost segment (default is 50).
width : float, optional
Waveguide width throughout the spiral (default is 2).
w_port : float, optional
IO waveguide width after the final taper (default is 0.45).
gap : float, optional
Spacing between successive turns (default is 1).
cycles : float, optional
Number of rectangular loops (default is 20).
xs : str, optional
Cross-section key (default is "strip").
layer : str or None, optional
Override polygon layer (default is None).
Lport : float, optional
Length of straight port extensions (default is 10).
in_out_align : bool, optional
Align input/output along the same axis when ``port_angle=180`` (default is True).
res : float, optional
Arc-length sampling resolution (default is 0.5).
cell_xs_transition : nd.Cell or object, optional
Transition cell appended at the ports (default is None).
port_angle : float, optional
Output bend angle in degrees (default is 180).
show_pins : bool, optional
Draw Nazca stub markers when True (default is False).
sharp_patch : bool, optional
Insert chamfer helpers when True (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=10,
in_out_align = True,
res: float = 0.5,
cell_xs_transition=None,
port_angle: float = 180,
show_pins: bool = False,
sharp_patch: bool = True):
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:
"""
Circular spiral with optional internal Euler S-bends and port transitions.
Parameters
----------
name : str or None, optional
Nazca cell name (default is None).
Dmin : float, optional
Minimum inner diameter in microns (default is 50).
width : float, optional
Nominal waveguide width (default is 2).
w_port : float, optional
Output-port width after the final taper (default is 0.45).
w_bend_center : float, optional
Waveguide width within the central attachment bend (default is 1).
gap : float, optional
Spacing between adjacent turns (default is 1).
cycles : float, optional
Number of half-turns (π radians) (default is 20).
xs : str, optional
Cross-section key (default is "strip").
layer : str or None, optional
Override layer for polygons (default is None).
Lport : float, optional
Length of straight sections appended to each port (default is 10).
res : float, optional
Arc-length sampling step (default is 0.5).
rib2strip : bool, optional
Insert rib-to-strip transitions at the ports (default is True).
port_angle : float, optional
Output bend angle in degrees (default is 180).
Euler_Sbend : bool, optional
Use optimized Euler S-bends at the center (default is False).
show_pins : bool, optional
Draw Nazca stub markers when True (default is False).
sharp_patch : bool, optional
Add chamfer polygons when True (default is True).
strict_condition : bool, optional
Enforce constant spacing by matching the conchoid tilt exactly (default is False).
R_ratio_mamnual : tuple or None, optional
Manually override the radius ratios used in Euler S-bends; expected form ``(Rc_ratio, Rm_ratio, tilt)``.
"""
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 = 10,
res : float = 0.5, ## added in 2023.1.4, the length resolution
rib2strip=True,
port_angle: float = 180,
Euler_Sbend: bool = False,
show_pins: bool = False,
sharp_patch:bool = True,
strict_condition = False,
R_ratio_mamnual = None,
):
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("")
SPR_U_INST = SPR_U.cell.put('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)
SPR_D_INST = SPR_D.cell.put('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)
self.Rmax = np.sqrt(SPR_D_INST.pin['b1'].x**2 + SPR_D_INST.pin['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
nd.taper(xs=self.xs,width1=self.w_port,width2=self.w_port,length=self.Lport).put(SPR_U_INST.pin['b1'])
nd.Pin(name='a1',width=self.w_port).put()
nd.taper(xs=self.xs,width1=self.w_port,width2=self.w_port,length=self.Lport).put(SPR_D_INST.pin['b1'])
nd.Pin(name='b1',width=self.w_port).put()
if show_pins:
nd.put_stub()
return C
class Spiral_Cicle_MM(spiral_circle):
"""
Parameters
----------
name : str or None, optional
Nazca cell name (default is None).
Dmin : float, optional
Minimum inner diameter in microns (default is 50).
width : float, optional
Nominal waveguide width (default is 2).
w_port : float, optional
Output-port width after the final taper (default is 0.45).
w_bend_center : float, optional
Waveguide width within the central attachment bend (default is 1).
gap : float, optional
Spacing between adjacent turns (default is 1).
cycles : float, optional
Number of half-turns (π radians) (default is 20).
xs : str, optional
Cross-section key (default is "strip").
layer : str or None, optional
Override polygon layer (default is None).
Lport : float, optional
Length of straight port extensions (default is 10).
res : float, optional
Arc-length sampling step (default is 0.5).
rib2strip : bool, optional
Insert rib-to-strip transitions at the ports (default is True).
port_angle : float, optional
Output bend angle in degrees (default is 180).
show_pins : bool, optional
Draw Nazca stub markers when True (default is False).
sharp_patch : bool, optional
Add chamfer polygons when True (default is True).
strict_condition : bool, optional
Enforce constant-spacing constraints when True (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=10,
res: float = 0.5,
rib2strip=True,
port_angle: float = 180,
show_pins: bool = False,
sharp_patch: bool = True,
strict_condition=False):
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):
"""
Parameters
----------
name : str or None, optional
Nazca cell name (default is None).
Dmin : float, optional
Minimum inner diameter in microns (default is 50).
width : float, optional
Waveguide width throughout the spiral (default is 2).
w_port : float, optional
Output-port width after the final taper (default is 0.45).
gap : float, optional
Spacing between adjacent turns (default is 1).
cycles : float, optional
Number of half-turns (π radians) (default is 20).
xs : str, optional
Cross-section key (default is "strip").
layer : str or None, optional
Override polygon layer (default is None).
Lport : float, optional
Length of straight port extensions (default is 10).
res : float, optional
Arc-length sampling step (default is 0.5).
rib2strip : bool, optional
Insert rib-to-strip transitions at the ports (default is True).
port_angle : float, optional
Output bend angle in degrees (default is 180).
show_pins : bool, optional
Draw Nazca stub markers when True (default is False).
sharp_patch : bool, optional
Add chamfer polygons when True (default is True).
strict_condition : bool, optional
Enforce constant-spacing constraints when True (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=10,
res: float = 0.5,
rib2strip=True,
port_angle: float = 180,
show_pins: bool = False,
sharp_patch: bool = True,
strict_condition=False):
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)