import nazca as nd import numpy as np from scipy.interpolate import CubicSpline from ...routing import Route from ...structures import * from ...foundries import * import pandas as pd from ...structures import _my_polygon from ...basic import __cell_arg__ class YBranch: """ Broadband spline-shaped Y-branch with two bent output ports. Parameters ---------- name : str or None, optional Nazca cell name. ``None`` keeps the cell uninstantiated (default is None). xs : str, optional Cross-section key used for both the taper body and attachments (default is "strip"). w : Sequence[float], optional Width control points (µm) used by the cubic spline along the taper axis. Length must be >= 2. Default is ``[1.2, 1.0, 1.8, 1.2, 1.0, 1.2, 1.2]``. L : float, optional Total spline length in microns (default is 6). R_att : float, optional Bend radius of each attachment waveguide in microns (default is 10). A_att : float, optional Bend angle (degrees) per attachment arc (default is 10). w_port : float, optional Output port width in microns (default is 0.45). show_pins : bool, optional Draw Nazca stub markers when True (default is False). sharp_patch : bool, optional Add chamfer helpers inside polygons when True (default is True). res : float, optional Longitudinal sampling pitch (µm) for polygon discretization (default is 0.1). """ def __init__(self, name : str = None, xs : str = 'strip', w : 'list|np.ndarray' = [1.2,1.0,1.8,1.2,1.0,1.2,1.2], L : float = 6, R_att : float = 10, A_att : float = 10, w_port : float = 0.45, show_pins : bool = False, sharp_patch : bool = True, res : float = 0.1, ) -> None: self.name = name if (name!=None): self.instantiate = True else : self.instantiate = False self.w = w self.L = L self.res = res self.R_att = R_att self.A_att = A_att self.w_port = w_port self.xs = xs self.cell = self.generate_gds(show_pins=show_pins,sharp_patch=sharp_patch) def generate_gds(self,show_pins=False,sharp_patch=True): with nd.Cell(name=self.name,instantiate=self.instantiate) as C: w = np.r_[self.w] n_sects = len(self.w)-1 res = self.L/n_sects n_points = int(self.L/self.res)+1 L = np.linspace(0,self.L,n_sects+1) L_act = np.linspace(0,self.L,n_points) f = CubicSpline(L,w) ## cubic spline interpolant w_act = f(L_act) for layers,growx,growy,acc in nd.layeriter(xs=self.xs): (a1,b1), (a2,b2),c1,c2 = growx w_cur = w_act*(a1-a2) + (b1-b2) if (b1!=0 and b2!=0): w_cur = max(w_cur)*np.ones(np.shape(w_cur)) vtx_x = np.r_[L_act,np.flip(L_act,0)] vtx_y = np.r_[w_cur/2,-np.flip(w_cur/2,0)] vtx = np.c_[vtx_x,vtx_y] _my_polygon(layer_wg=layers,vtx=vtx).put(0,0,0) temp = circle(xs=self.xs,radius=self.R_att,theta_start=0,theta_stop=self.A_att,width=self.w_port).cell.put('a1',self.L,self.w[-1]/2 - self.w_port/2,0) temp = circle(xs=self.xs,radius=self.R_att,theta_start=-self.A_att,theta_stop=0,width=self.w_port).cell.put('a1',temp.pin['b1'],flip=1) nd.Pin(name='b1',width=self.w_port).put(temp.pin['b1']) temp = circle(xs=self.xs,radius=self.R_att,theta_start=0,theta_stop=self.A_att,width=self.w_port).cell.put('a1',self.L,-self.w[-1]/2 + self.w_port/2,0,flip=1) temp = circle(xs=self.xs,radius=self.R_att,theta_start=-self.A_att,theta_stop=0,width=self.w_port).cell.put('a1',temp.pin['b1'],flip=0) nd.Pin(name='b2',width=self.w_port).put(temp.pin['b1']) nd.Pin(name='a1',width=self.w[0]).put(0,0,180) if (show_pins): nd.put_stub() return C class Ybranch_3wg: """ Initialization of a symmetric tapered coupler for 3dB coupling Parameters ---------- name : str or None, optional Nazca cell name (default is None). 1. taper part w0 : float, optional Width (µm) of the center arm at the coupling region entrance (default is 0.4). w1 : float, optional Width (µm) of the outer arms at the coupling region exit (default is 0.2). gap : float, optional Vertical spacing (µm) between adjacent arms inside the coupler (default is 0.18). Lcp : float, optional Length (µm) of each taper section forming the coupler (default is 20). xs : str, optional Cross-section key for all segments (default is "strip"). 2. attachment part w_wg : float, optional External IO waveguide width in microns (default is 0.45). R0 : float, optional Bend radius (µm) used for both output waveguides (default is 10). angle : float, optional Bend deflection angle in degrees (default is 20). L_attach : float, optional Length (µm) of straight sections appended after the output tapers (default is 3). L_in_tp : float, optional Taper length (µm) that links the IO waveguide to width ``w0`` (default is 3). sharp_patch : bool, optional Insert chamfer helpers when True (default is True). """ def __init__(self, name = None, w0:float=0.4, w1:float=0.2, gap:float=0.18, Lcp:float=20, xs:str='strip', w_wg:float=0.45, R0:float=10, angle:float=20, L_attach:float=3, L_in_tp:float=3, sharp_patch:bool=True): self.name = name if (self.name==None): self.instantiate = False else : self.instantiate = True self.w0 = w0 self.w1 = w1 self.gap = gap self.Lcp = Lcp self.xs = xs self.w_wg = w_wg self.R0 = R0 self.angle = angle self.L_attach = L_attach self.L_in_tp = L_in_tp self.cell = self.generate_gds(sharp_patch=sharp_patch) self.L = np.abs(self.cell.pin['a1'].x - self.cell.pin['b1'].x) def generate_gds(self,sharp_patch,err_asy=0): with nd.Cell(instantiate=self.instantiate,name=self.name) as C: w0 = self.w0 w1 = self.w1 Lcp = self.Lcp gap = self.gap xs = self.xs w_wg = self.w_wg L_attach = self.L_attach L_in_tp = self.L_in_tp angle = self.angle R0 = self.R0 t_mid = nd.taper(width1=w0,width2=w1,length=Lcp,xs=xs).put(0,0,0) t_u = nd.taper(width2=w0,width1=w1,length=Lcp,xs=xs).put(0,w1/2+w0/2+gap,0) t_d = nd.taper(width2=w0,width1=w1,length=Lcp,xs=xs).put(0,-(w1/2+w0/2+gap),0) t_in = nd.taper(width1=w_wg,width2=w0,length=L_in_tp,xs=xs).put(-L_in_tp,0,0) t_in = nd.strt(width=w_wg,length=L_attach,xs=xs).put(t_in.pin['a0'],flip=0) nd.Pin(name='a1',pin=t_in.pin['b0']).put() au = nd.bend(radius=R0,angle=angle,xs=xs,width=w0).put(t_u.pin['b0'],flip=0) au = nd.bend(radius=R0,angle=angle,xs=xs,width=w0).put(au.pin['b0'],flip=1) au = nd.taper(width1=w0,width2=w_wg,length=L_in_tp,xs=xs).put(au.pin['b0'],flip=0) au = nd.strt(width=w_wg,length=L_attach,xs=xs).put(au.pin['b0'],flip=0) nd.Pin(name='b1',pin=au.pin['b0']).put() ad = nd.bend(radius=R0,angle=angle,xs=xs,width=w0).put(t_d.pin['b0'],flip=1) ad = nd.bend(radius=R0,angle=angle,xs=xs,width=w0).put(ad.pin['b0'],flip=0) ad = nd.taper(width1=w0,width2=w_wg,length=L_in_tp,xs=xs).put(ad.pin['b0'],flip=0) ad = nd.strt(width=w_wg,length=L_attach,xs=xs).put(ad.pin['b0'],flip=0) nd.Pin(name='b2',pin=ad.pin['b0']).put() if (sharp_patch==True): dY = np.abs(ad.pin['b0'].y-au.pin['b0'].y)+w_wg for layers,growx,growy,acc in nd.layeriter(xs=xs): (a1,b1), (a2,b2),c1,c2 = growx if (b1!=0 and b2!=0): L_patch = dY*(a1-a2)+(b1-b2) W_patch = dY*(a1-a2)+(b1-b2) nd.strt(length=W_patch,width=L_patch,layer=layers).put(ad.pin['b0'].x,0,0) return C def generate_test_gds(self,gc,dX_gc2gc=400,dY_gc2gc=80,sharp_patch = True,Rbend=15): with nd.Cell(instantiate=False) as C: gc_cell = __cell_arg__(arg=gc,arg_name="gc",func_name="mxpic::Ybranch_3wg::generate_test_gds") inst = self.cell.put('a1',-self.L/2,0,0) gc_In = gc_cell.put('g1',-dX_gc2gc/2,0,180) gc_O1 = gc_cell.put('g1',dX_gc2gc/2, dY_gc2gc/2,0) gc_O2 = gc_cell.put('g1',dX_gc2gc/2,-dY_gc2gc/2,0) pic_strip = Route(radius=Rbend,width=self.w_wg,xs=self.xs) pic_strip.taper(pin=gc_O1.pin['g1'],width1=gc_O1.pin['g1'].width,width2=self.w_wg,length=5,arrow=False) pic_strip.sbend_p2p(original_function=not sharp_patch, pin2=inst.pin['b1'],arrow=False).put() pic_strip.taper(pin=gc_O2.pin['g1'],width1=gc_O2.pin['g1'].width,width2=self.w_wg,length=5,arrow=False) pic_strip.sbend_p2p(original_function=not sharp_patch, pin2=inst.pin['b2'],arrow=False).put() pic_strip.taper_p2p(pin1=gc_In.pin['g1'],pin2=inst.pin['a1'],arrow=False).put() return C