from turtle import shape import nazca as nd import numpy as np import math from ...routing import Route from ...structures import * from ...structures import _my_polygon,Conchoid """ Mono layer MMI """ class MMI_ML: """ Multi-layer (mono-layer) multimode interference (MMI) device generator. Parameters ---------- name : str or None, optional Nazca cell name. ``None`` keeps the cell uninstantiated (default is None). L_arm : Sequence[float], optional Segment lengths (µm) of each arm taper section (default is ``[10]``). w_arm : Sequence[float], optional Corresponding arm widths (µm). Length must be ``len(L_arm) + 1`` (default is ``[0.45, 1.35]``). xs : str, optional Nazca cross-section key used for both arm and MMI regions (default is "strip"). arm_sine_width : bool, optional If True, arm width follows a cosine taper instead of linear interpolation (default is False). L_mmi : Sequence[float], optional Segment lengths (µm) within the central MMI body (default is ``[10]``). w_mmi : Sequence[float], optional MMI widths (µm). Length must be ``len(L_mmi) + 1`` (default is ``[5, 5]``). mmi_sine_width : bool, optional If True, MMI width transition uses cosine instead of linear interpolation (default is False). sharp_patch : bool, optional Insert chamfer polygons at acute corners when ``True`` (default is True). show_pins : bool, optional Draw Nazca stub markers for debugging when ``True`` (default is False). res : float, optional Longitudinal sampling resolution (µm) for polygon generation (default is 0.01). N_out : int, optional Number of output ports (default is 3). N_in : int, optional Number of input ports (default is 1). Dp_out : float, optional Vertical pitch (µm) between adjacent output ports (default is 1.5). Dp_in : float, optional Vertical pitch (µm) between adjacent input ports (default is 1.5). """ def __init__(self, name=None, L_arm=[10], w_arm=[0.45,1.35], xs = 'strip', arm_sine_width=False, L_mmi = [10], w_mmi = [5,5], mmi_sine_width=False, sharp_patch=True, show_pins = False, res = 0.01, N_out = 3, N_in = 1, Dp_out = 1.5, Dp_in = 1.5, ) -> None: self.name = name if (self.name==None): self.instantiate = False else : self.instantiate = True self.L_arm = L_arm self.xs = xs self.w_arm = w_arm self.arm_sine_width = arm_sine_width self.L_mmi = L_mmi self.w_mmi = w_mmi self.res = res self.N_out = N_out self.N_in = N_in self.Dp_out = Dp_out self.Dp_in = Dp_in self.mmi_sine_width = mmi_sine_width self.cell = self.generate_gds(sharp_patch=sharp_patch,show_pins=show_pins) self.L = np.sum(self.L_arm)*2+np.sum(self.L_mmi) def generate_gds(self,sharp_patch,show_pins): with nd.Cell(instantiate=self.instantiate,name=self.name) as C: L = 0 Lsg = [] Wsg = [] for idx in range(0,len(self.L_arm)): n_points = round(self.L_arm[idx]/self.res)+1 L_sect = np.linspace(L,L+self.L_arm[idx],n_points) Lsg = np.r_[Lsg,L_sect] if (self.arm_sine_width): dw = self.w_arm[idx+1]-self.w_arm[idx] w_sect = -np.cos(L_sect/self.L_arm[idx]*pi)*dw + (self.w_arm[idx+1]-self.w_arm[idx])/2 else: w_sect = np.linspace(self.w_arm[idx],self.w_arm[idx+1],n_points) Wsg = np.r_[Wsg,w_sect] L = L + self.L_arm[idx] with nd.Cell(instantiate=False) as Arm: for layers,growx,growy,acc in nd.layeriter(xs=self.xs): (a1,b1), (a2,b2),c1,c2 = growx vtx_y = np.r_[Wsg*a1+b1, np.flip(Wsg,0)*a2+b2] vtx_x = np.r_[Lsg, np.flip(Lsg,0)] vtx = np.c_[vtx_x,vtx_y] _my_polygon(layer_wg=layers,vtx=vtx).put(0,0,0) nd.Pin(name='a1',width=Wsg[0]).put(0,0,180) nd.Pin(name='b1',width=Wsg[-1]).put(L,0,0) """ For central MMI """ L_mmi = 0 Lsg_mmi = [] Wsg_mmi = [] for idx in range(0,len(self.L_mmi)): n_points = round(self.L_mmi[idx]/self.res)+1 L_sect = np.linspace(L_mmi,L_mmi+self.L_mmi[idx],n_points) Lsg_mmi = np.r_[Lsg_mmi,L_sect] if (self.arm_sine_width): dw = self.w_mmi[idx+1]-self.w_mmi[idx] w_sect = -np.cos(L_sect/self.L_mmi[idx]*pi)*dw + (self.w_mmi[idx+1]-self.w_mmi[idx])/2 else: w_sect = np.linspace(self.w_mmi[idx],self.w_mmi[idx+1],n_points) Wsg_mmi = np.r_[Wsg_mmi,w_sect] L_mmi = L_mmi + self.L_mmi[idx] with nd.Cell(instantiate=False) as MMI: for layers,growx,growy,acc in nd.layeriter(xs=self.xs): (a1,b1), (a2,b2),c1,c2 = growx vtx_y = np.r_[Wsg_mmi*a1+b1, np.flip(Wsg_mmi,0)*a2+b2] vtx_x = np.r_[Lsg_mmi, np.flip(Lsg_mmi,0)] vtx = np.c_[vtx_x,vtx_y] if (b1==0 and b2==0): _my_polygon(layer_wg=layers,vtx=vtx).put(0,0,0) else : w = max(Wsg_mmi)+b1*2 L = max(Lsg_mmi)+b1*2 nd.strt(length=L,layer=layers,width=w).put(-b1,0,0) nd.Pin(name='a1',width=Wsg_mmi[0]).put(0,0,180) nd.Pin(name='b1',width=Wsg_mmi[-1]).put(L_mmi,0,0) for idx_in in range(0,self.N_in): Arm_inst = Arm.put('b1',0,self.Dp_in*(-idx_in+(self.N_in-1)/2),180) nd.Pin(name='a'+str(round(idx_in+1)),pin=Arm_inst.pin['a1']).put() for idx_in in range(0,self.N_out): Arm_inst = Arm.put('b1',L_mmi,self.Dp_out*(-idx_in+(self.N_out-1)/2),0) nd.Pin(name='b'+str(round(idx_in+1)),pin=Arm_inst.pin['a1']).put() MMI.put('a1',0,0,0) if (show_pins): nd.put_stub() return C def generate_test_gds(self,gc,dX_gc2gc,dY_gc2gc,R_bend=10,Xout_offset=50): if (isinstance(gc,nd.Cell)): gc_cell =gc elif (hasattr(gc,'cell')): gc_cell = gc.cell else : raise Exception("ERROR: In , is not recongized as a cell") with nd.Cell(instantiate=False) as C: INST = self.cell.put(-self.L/2,0,0) pic_strip = Route(width=self.w_arm[0],radius=R_bend,xs=self.xs) for idx_in in range(0,self.N_in): GC = gc_cell.put('g1',-dX_gc2gc/2,dY_gc2gc*(-idx_in + (self.N_in-1)/2),180) pic_strip.sbend_p2p(pin1=GC.pin['g1'],pin2=INST.pin['a'+str(idx_in+1)],Lstart=dX_gc2gc/10).put() for idx_in in range(0,self.N_out): toggle = np.mod(idx_in,2)-0.5 GC = gc_cell.put('g1', dX_gc2gc/2+Xout_offset*toggle,dY_gc2gc*(-idx_in + (self.N_out-1)/2),0) pic_strip.sbend_p2p(pin1=GC.pin['g1'],pin2=INST.pin['b'+str(idx_in+1)],Lstart=dX_gc2gc/10).put() return C class MMI_STD(MMI_ML): """ Convenience wrapper for standard MMIs with equal-length arms and uniform MMI body. Parameters ---------- name : str or None, optional Nazca cell name (default is None). N_out : int, optional Number of output ports (default is 3). N_in : int, optional Number of input ports (default is 1). L_arm : float, optional Single arm length in microns (default is 10). w_wg : float, optional Input/output waveguide width in microns (default is 0.45). w_port : float, optional Width at the transition between the taper and MMI (default is 1.2). xs : str, optional Cross-section key for all regions (default is "strip"). L_mmi : float, optional Central MMI length in microns (default is 10). w_mmi : float, optional Central MMI width in microns (default is 5). sharp_patch : bool, optional Add chamfer helpers when True (default is True). show_pins : bool, optional Draw Nazca stub markers when True (default is False). Dp_out : float, optional Output port pitch in microns (default is 1.5). Dp_in : float, optional Input port pitch in microns (default is 1.5). """ def __init__(self, name=None, N_out=3, N_in=1, L_arm=10, w_wg=0.45, w_port = 1.2, xs='strip', L_mmi=10, w_mmi=5, sharp_patch=True, show_pins=False, Dp_out=1.5, Dp_in=1.5) -> None: super().__init__(name=name, L_arm=[L_arm], w_arm=[w_wg,w_port], xs=xs, arm_sine_width=False, L_mmi=[L_mmi], w_mmi=[w_mmi,w_mmi], mmi_sine_width=False, sharp_patch=sharp_patch, show_pins=show_pins, res=min([L_mmi,L_arm]), ## taper resolution N_out=N_out, N_in=N_in, Dp_out=Dp_out, Dp_in=Dp_in)