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