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, 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 , 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