def arc_bezier(radius, start, stop, bezier, DevRec=None): from math import sin, cos, pi from SiEPIC.utils import points_per_circle N = points_per_circle(radius / 1000) / 4 if DevRec: N = int(N / 3) else: N = int(N) if N < 5: N = 100 L = radius # effective bend radius / Length of the bend diff = 1. / (N - 1) # convert int to float xp = [0, (1 - bezier) * L, L, L] yp = [0, 0, bezier * L, L] xA = xp[3] - 3 * xp[2] + 3 * xp[1] - xp[0] xB = 3 * xp[2] - 6 * xp[1] + 3 * xp[0] xC = 3 * xp[1] - 3 * xp[0] xD = xp[0] yA = yp[3] - 3 * yp[2] + 3 * yp[1] - yp[0] yB = 3 * yp[2] - 6 * yp[1] + 3 * yp[0] yC = 3 * yp[1] - 3 * yp[0] yD = yp[0] pts = [pya.Point(-L, 0) + pya.Point(xD, yD)] for i in range(1, N - 1): t = i * diff pts.append( pya.Point(-L, 0) + pya.Point(t**3 * xA + t**2 * xB + t * xC + xD, t**3 * yA + t**2 * yB + t * yC + yD)) pts.extend([pya.Point(0, L - 1), pya.Point(0, L)]) return pts
def arc_wg_xy(x, y, r, w, theta_start, theta_stop, DevRec=None): # function to draw an arc of waveguide # x, y: location of the origin # r: radius # w: waveguide width # length units in dbu # theta_start, theta_stop: angles for the arc # angles in degrees from math import pi, cos, sin from . import points_per_circle circle_fraction = abs(theta_stop - theta_start) / 360.0 npoints = int(points_per_circle(r / 1000) * circle_fraction) if DevRec: npoints = int(npoints / 3) if npoints == 0: npoints = 1 da = 2 * pi / npoints * circle_fraction # increment, in radians pts = [] th = theta_start / 360.0 * 2 * pi for i in range(0, npoints + 1): pts.append( pya.Point.from_dpoint( pya.DPoint((x + (r + w / 2) * cos(i * da + th)) / 1, (y + (r + w / 2) * sin(i * da + th)) / 1))) for i in range(npoints, -1, -1): pts.append( pya.Point.from_dpoint( pya.DPoint((x + (r - w / 2) * cos(i * da + th)) / 1, (y + (r - w / 2) * sin(i * da + th)) / 1))) return pya.Polygon(pts)
def layout_waveguide_rel(cell, layer, start_point, points, w, radius): # create a path, then convert to a polygon waveguide with bends # cell: cell into which to place the waveguide # layer: layer to draw on # start_point: starting vertex for the waveguide # points: array of vertices, relative to start_point # w: waveguide width # example usage: # cell = pya.Application.instance().main_window().current_view().active_cellview().cell # LayerSi = LayerInfo(1, 0) # points = [ [15, 2.75], [30, 2.75] ] # units of microns. # layout_waveguide_rel(cell, LayerSi, [0,0], points, 0.5, 10) #print("* layout_waveguide_rel(%s, %s, %s, %s)" % (cell.name, layer, w, radius) ) ly = cell.layout() dbu = cell.layout().dbu start_point = [start_point[0] / dbu, start_point[1] / dbu] a1 = [] for p in points: a1.append(pya.DPoint(float(p[0]), float(p[1]))) wg_path = pya.DPath(a1, w) npoints = points_per_circle(radius / dbu) param = { "npoints": npoints, "radius": float(radius), "path": wg_path, "layer": layer } pcell = ly.create_cell("ROUND_PATH", "Basic", param) # Configure the cell location trans = Trans(Point(start_point[0], start_point[1])) # Place the PCell cell.insert(pya.CellInstArray(pcell.cell_index(), trans))
def arc(r, theta_start, theta_stop): # function to draw an arc of waveguide # radius: radius # w: waveguide width # length units in dbu # theta_start, theta_stop: angles for the arc # angles in degrees from math import pi, cos, sin from . import points_per_circle circle_fraction = abs(theta_stop - theta_start) / 360.0 npoints = int(points_per_circle(r/1000) * circle_fraction) if npoints == 0: npoints = 1 da = 2 * pi / npoints * circle_fraction # increment, in radians pts = [] th = theta_start / 360.0 * 2 * pi for i in range(0, npoints + 1): pts.append(pya.Point.from_dpoint(pya.DPoint( (r * cos(i * da + th)) / 1, (r * sin(i * da + th)) / 1))) return pts
def produce_impl(self): # fetch the parameters dbu = self.layout.dbu ly = self.layout shapes = self.cell.shapes TECHNOLOGY = get_technology_by_name('EBeam') LayerSi = self.layer LayerSiN = ly.layer(LayerSi) LayerSiSPN = ly.layer(LayerSi) LayerPinRecN = ly.layer(self.pinrec) LayerDevRecN = ly.layer(self.devrec) LayerTextN = TECHNOLOGY['Text'] ######## effective index function ########## def effective_index(wl=self.wavelength, etch_depth=self.etch_depth, Si_thickness=self.Si_thickness, n_t=self.n_t, pol=self.pol, dc=self.dc): from math import pi, cos, sin, log, sqrt, tan from SiEPIC.utils import points_per_circle point = 1001 n_0 = n_t n_1 = 0 n_3 = 1.444 n_2 = sqrt(7.9874 + (3.68 * pow(3.9328, 2) * pow(10, 30)) / ((pow(3.9328, 2) * pow(10, 30) - pow(2 * 3.14 * 3 * pow(10, 8) / (wl * pow(10, -6)), 2))) ) # Silicon wavelength-dependant index of refraction delta = n_0 - n_3 t = Si_thickness t_slot = t - etch_depth k_0 = 2 * pi / wl b_0 = linspace_without_numpy(0, 0, point - 1) te_0 = linspace_without_numpy(0, 0, point - 1) te_1 = linspace_without_numpy(0, 0, point - 1) tm_0 = linspace_without_numpy(0, 0, point - 1) tm_1 = linspace_without_numpy(0, 0, point - 1) h_0 = linspace_without_numpy(0, 0, point - 1) q_0 = linspace_without_numpy(0, 0, point - 1) p_0 = linspace_without_numpy(0, 0, point - 1) qbar_0 = linspace_without_numpy(0, 0, point - 1) pbar_0 = linspace_without_numpy(0, 0, point - 1) # calculating neff for the silicon layer if delta < 0: n_1 = n_3 else: n_1 = n_0 for ii in range(0, point - 1): b_0[ii] = n_1 * k_0 + (n_2 - n_1) * k_0 / ( point - 1) * ii # copied from .ample UGC: should this be point-1? h_0[ii] = sqrt(abs(pow(n_2 * k_0, 2) - pow(b_0[ii], 2))) q_0[ii] = sqrt(abs(pow(b_0[ii], 2) - pow(n_0 * k_0, 2))) p_0[ii] = sqrt(abs(pow(b_0[ii], 2) - pow(n_3 * k_0, 2))) pbar_0[ii] = pow(n_2 / n_3, 2) * p_0[ii] qbar_0[ii] = pow(n_2 / n_0, 2) * q_0[ii] # calculating neff for TE mode if pol == "TE": for ii in range(0, point - 1): te_0[ii] = tan( h_0[ii] * t) - (p_0[ii] + q_0[ii]) / h_0[ii] / ( 1 - p_0[ii] * q_0[ii] / pow(h_0[ii], 2)) te_1[ii] = tan( h_0[ii] * t_slot) - (p_0[ii] + q_0[ii]) / h_0[ii] / ( 1 - p_0[ii] * q_0[ii] / pow(h_0[ii], 2)) abs_te_0 = [abs(x) for x in te_0] abs_te_1 = [abs(x) for x in te_1] index_TE_0 = abs_te_0.index(min(abs_te_0)) index_TE_1 = abs_te_1.index(min(abs_te_1)) nTE_0 = b_0[index_TE_0] / k_0 nTE_1 = b_0[index_TE_1] / k_0 while (nTE_0 < 2 or nTE_0 > 3): abs_te_0[index_TE_0] = 100 index_TE_0 = abs_te_0.index(min(abs_te_0)) nTE_0 = b_0[index_TE_0] / k_0 while (nTE_1 < 2 or nTE_1 > 3): abs_te_1[index_TE_1] = 100 index_TE_1 = abs_te_1.index(min(abs_te_1)) nTE_1 = b_0[index_TE_1] / k_0 ne = dc * nTE_0 + (1 - dc) * nTE_1 # calculating neff for TE mode elif pol == "TM": for ii in range(0, point - 1): tm_0[ii] = tan( h_0[ii] * t) - (pbar_0[ii] + qbar_0[ii]) / h_0[ii] / ( 1 - pbar_0[ii] * qbar_0[ii] / pow(h_0[ii], 2)) tm_1[ii] = tan( h_0[ii] * t_slot) - (pbar_0[ii] + qbar_0[ii]) / h_0[ii] / ( 1 - pbar_0[ii] * qbar_0[ii] / pow(h_0[ii], 2)) abs_tm_0 = [abs(x) for x in tm_0] abs_tm_1 = [abs(x) for x in tm_1] index_TM_0 = abs_tm_0.index(min(abs_tm_0)) index_TM_1 = abs_tm_1.index(min(abs_tm_1)) nTM_0 = b_0[index_TM_0] / k_0 nTM_1 = b_0[index_TM_1] / k_0 while (nTM_0 < 1.5 or nTM_0 > 3): abs_tm_0[index_TM_0] = 100 index_TM_0 = abs_tm_0.index(min(abs_tm_0)) nTM_0 = b_0[index_TM_0] / k_0 while (nTM_1 < 1.5 or nTM_1 > 3): abs_tm_1[index_TM_1] = 100 index_TM_1 = abs_tm_1.index(min(abs_tm_1)) nTM_1 = b_0[index_TM_1] / k_0 ne = dc * nTM_0 + (1 - dc) * nTM_1 else: print('Please type TE or TM for polarization') return ne ##################################### from math import pi, cos, sin, log, sqrt, tan from SiEPIC.utils import points_per_circle lambda_0 = self.wavelength ##um wavelength of light n_e = effective_index() ne_fiber = 1 # effective index of the mode in the air period = self.wavelength / (n_e - sin(pi / 180 * self.theta_c) * ne_fiber) # Geometry wh = period * self.dc ##thick grating gc_number = int(round(self.grating_length / period)) ##number of periods e = self.n_t * sin((pi / 180) * self.theta_c) / n_e N = round(self.taper_length * (1 + e) * n_e / lambda_0) ##allows room for the taper start = (pi - (pi / 180) * self.angle_e / 2) stop = (pi + (pi / 180) * self.angle_e / 2) # Draw coupler grating. for j in range(gc_number): # number of points in the arcs: # calculate such that the vertex & edge placement error is < 0.5 nm. # see "SiEPIC_EBeam_functions - points_per_circle" for more details radius = N * lambda_0 / (n_e * (1 - e)) + j * period seg_points = int( points_per_circle(radius / dbu) / 360. * self.angle_e) # number of points grating arc theta_up = [] for m in range(seg_points + 1): theta_up = theta_up + [start + m * (stop - start) / seg_points] theta_down = theta_up[::-1] ##big one r_up = [] r_down = [] for k in range(len(theta_up)): r_up = r_up + [ N * lambda_0 / (n_e * (1 - e * cos(float(theta_up[k])))) + j * period + period * (1 - self.dc) ] r_down = r_up[::-1] xr = [] yr = [] for k in range(len(theta_up)): xr = xr + [r_up[k] * cos(theta_up[k])] yr = yr + [r_up[k] * sin(theta_up[k])] xl = [] yl = [] for k in range(len(theta_down)): xl = xl + [(r_down[k] + wh) * cos(theta_down[k])] yl = yl + [(r_down[k] + wh) * sin(theta_down[k])] x = xr + xl y = yr + yl pts = [] for i in range(len(x)): pts.append(Point.from_dpoint(DPoint(x[i] / dbu, y[i] / dbu))) polygon = Polygon(pts) shapes(LayerSiN).insert(polygon) # Taper section r_up = [] r_down = [] for k in range(len(theta_up)): r_up = r_up + [ N * lambda_0 / (n_e * (1 - e * cos(float(theta_up[k])))) ] r_down = r_up[::-1] xl = [] yl = [] for k in range(len(theta_down)): xl = xl + [(r_down[k]) * cos(theta_down[k])] yl = yl + [(r_down[k]) * sin(theta_down[k])] yr = [self.t / 2., -self.t / 2.] yl_abs = [] for k in range(len(yl)): yl_abs = yl_abs + [abs(yl[k])] y_max = max(yl_abs) iy_max = yl_abs.index(y_max) L_o = (y_max - self.t / 2) / tan((pi / 180) * self.angle_e / 2) xr = [0, 0] x = xr + xl y = yr + yl pts = [] for i in range(len(x)): pts.append(Point.from_dpoint(DPoint(x[i] / dbu, y[i] / dbu))) polygon = Polygon(pts) shapes(LayerSiN).insert(polygon) # Pin on the waveguide: from SiEPIC._globals import PIN_LENGTH as pin_length x = 0 t = Trans(Trans.R0, x, 0) pin = Path([Point(-pin_length / 2, 0), Point(pin_length / 2, 0)], self.t / dbu) pin_t = pin.transformed(t) shapes(LayerPinRecN).insert(pin_t) text = Text("pin1", t) shape = shapes(LayerPinRecN).insert(text) shape.text_size = 0.4 / dbu # Reference information t = Trans(Trans.R0, 0, -4000) text = Text( "Ref: 'Universal grating coupler design'\nhttps://doi.org/10.1117/12.2042185\nPCell implementation by: Yun Wang, Timothy Richards, Adam DeAbreu,\nJonas Flueckiger, Charlie Lin, Lukas Chrostowski, Connor Mosquera", t) shape = shapes(LayerPinRecN).insert(text) shape.text_size = 0.4 / dbu shape.text_halign = 2 # right alignment t = Trans(Trans.R0, 0, 4000) text = Text( "Wavelength: %s\nIncident Angle: %s\nPolarization: %s\nSilicon thickness: %s\nSilicon etch depth: %s" % (self.wavelength, self.theta_c, self.pol, self.Si_thickness, self.etch_depth), t) shape = shapes(LayerPinRecN).insert(text) shape.text_size = 0.4 / dbu shape.text_halign = 2 # right alignment shape.text_valign = 2 # bottom alignment # Device recognition layer yr = sin(start) * (N * lambda_0 / (n_e * (1 - e * cos(float(start)))) + gc_number * period) box1 = Box( -(self.grating_length + self.taper_length) / dbu - pin_length * 2, yr / dbu, 0, -yr / dbu) shapes(LayerDevRecN).insert(box1)
def layout_waveguide_sbend(cell, layer, trans, w=500, r=25000, h=2000, length=15000): """ Lays out an s-bend Args: trans: pya.Trans: location and rotation w: width of waveguide, float r: radius, float h: height, float length: length, float """ from math import pi, cos, sin, log, sqrt, acos from SiEPIC.utils import points_per_circle import pya theta = acos(float(r-abs(h/2))/r)*180/pi x = 2*r*sin(theta/180.0*pi) straight_l = (length - x)/2 if (straight_l < 0): # Problem: target length is too short. increase print('SBend, too short: straight_l = %s' % straight_l) length += -straight_l + 1 straight_l = 1 waveguide_length = (2*pi*r*(2*theta/360.0)+straight_l*2) # print('SBend: theta %s, x %s, straight_l %s, r %s, h %s' % (theta, x, straight_l, r, h) ) # define the cell origin as the left side of the waveguide sbend if (straight_l >= 0): circle_fraction = abs(theta) / 360.0 npoints = int(points_per_circle(r) * circle_fraction) if npoints == 0: npoints = 1 da = 2 * pi / npoints * circle_fraction # increment, in radians x1=straight_l x2=length-straight_l if h>0: y1=r theta_start1 = 270 y2=h-r theta_start2 = 90 pts = [] th1 = theta_start1 / 360.0 * 2 * pi th2 = theta_start2 / 360.0 * 2 * pi pts.append(pya.Point.from_dpoint(pya.DPoint(0,w/2))) pts.append(pya.Point.from_dpoint(pya.DPoint(0,-w/2))) for i in range(0, npoints+1): # lower left pts.append(pya.Point.from_dpoint(pya.DPoint((x1+(r+w/2)*cos(i*da+th1))/1, (y1+(r+w/2)*sin(i*da+th1))/1))) for i in range(npoints, -1, -1): # lower right pts.append(pya.Point.from_dpoint(pya.DPoint((x2+(r-w/2)*cos(i*da+th2))/1, (y2+(r-w/2)*sin(i*da+th2))/1))) pts.append(pya.Point.from_dpoint(pya.DPoint(length,h-w/2))) pts.append(pya.Point.from_dpoint(pya.DPoint(length,h+w/2))) for i in range(0, npoints+1): # upper right pts.append(pya.Point.from_dpoint(pya.DPoint((x2+(r+w/2)*cos(i*da+th2))/1, (y2+(r+w/2)*sin(i*da+th2))/1))) for i in range(npoints, -1, -1): # upper left pts.append(pya.Point.from_dpoint(pya.DPoint((x1+(r-w/2)*cos(i*da+th1))/1, (y1+(r-w/2)*sin(i*da+th1))/1))) else: y1=-r theta_start1 = 90-theta y2=r+h theta_start2 = 270-theta pts = [] th1 = theta_start1 / 360.0 * 2 * pi th2 = theta_start2 / 360.0 * 2 * pi pts.append(pya.Point.from_dpoint(pya.DPoint(length,h-w/2))) pts.append(pya.Point.from_dpoint(pya.DPoint(length,h+w/2))) for i in range(npoints, -1, -1): # upper right pts.append(pya.Point.from_dpoint(pya.DPoint((x2+(r-w/2)*cos(i*da+th2))/1, (y2+(r-w/2)*sin(i*da+th2))/1))) for i in range(0, npoints+1): # upper left pts.append(pya.Point.from_dpoint(pya.DPoint((x1+(r+w/2)*cos(i*da+th1))/1, (y1+(r+w/2)*sin(i*da+th1))/1))) pts.append(pya.Point.from_dpoint(pya.DPoint(0,w/2))) pts.append(pya.Point.from_dpoint(pya.DPoint(0,-w/2))) for i in range(npoints, -1, -1): # lower left pts.append(pya.Point.from_dpoint(pya.DPoint((x1+(r-w/2)*cos(i*da+th1))/1, (y1+(r-w/2)*sin(i*da+th1))/1))) for i in range(0, npoints+1): # lower right pts.append(pya.Point.from_dpoint(pya.DPoint((x2+(r+w/2)*cos(i*da+th2))/1, (y2+(r+w/2)*sin(i*da+th2))/1))) cell.shapes(layer).insert(pya.Polygon(pts).transformed(trans)) return waveguide_length
def produce_impl(self): # fetch the parameters dbu = self.layout.dbu ly = self.layout shapes = self.cell.shapes LayerSi = self.layer LayerSiN = ly.layer(LayerSi) LayerSiSPN = ly.layer(LayerSi) LayerPinRecN = ly.layer(self.pinrec) LayerDevRecN = ly.layer(self.devrec) from math import pi, cos, sin, log, sqrt, tan from SiEPIC.utils import points_per_circle lambda_0 = self.wavelength ##um wavelength of light # pin_length =0.0 ##um extra nub for the waveguide attachment # Geometry wh = self.period * self.dc ##thick grating wl = self.ff * (self.period - wh) ## thin grating spacing = (self.period - wh - wl) / 2 ##space between thick and thin gc_number = int(round(self.grating_length / self.period)) ##number of periods e = self.n_t * sin((pi / 180) * self.theta_c) / self.n_e N = round(self.taper_length * (1 + e) * self.n_e / lambda_0) ##allows room for the taper start = (pi - (pi / 180) * self.angle_e / 2) stop = (pi + (pi / 180) * self.angle_e / 2) # Draw coupler grating. for j in range(gc_number): # number of points in the arcs: # calculate such that the vertex & edge placement error is < 0.5 nm. # see "SiEPIC_EBeam_functions - points_per_circle" for more details radius = N * lambda_0 / (self.n_e * (1 - e)) + j * self.period + spacing seg_points = int( points_per_circle(radius / dbu) / 360. * self.angle_e) # number of points grating arc theta_up = [] for m in range(seg_points + 1): theta_up = theta_up + [start + m * (stop - start) / seg_points] theta_down = theta_up[::-1] ##small one r_up = [] r_down = [] for k in range(len(theta_up)): r_up = r_up + [ N * lambda_0 / (self.n_e * (1 - e * cos(float(theta_up[k])))) + j * self.period + spacing ] r_down = r_up[::-1] xr = [] yr = [] for k in range(len(theta_up)): xr = xr + [r_up[k] * cos(theta_up[k])] yr = yr + [r_up[k] * sin(theta_up[k])] xl = [] yl = [] for k in range(len(theta_down)): xl = xl + [(r_down[k] + wl) * cos(theta_down[k])] yl = yl + [(r_down[k] + wl) * sin(theta_down[k])] x = xr + xl y = yr + yl pts = [] for i in range(len(x)): pts.append(Point.from_dpoint(DPoint(x[i] / dbu, y[i] / dbu))) #small_one = core.Boundary(points) polygon = Polygon(pts) if self.ff > 0: shapes(LayerSiN).insert(polygon) ##big one r_up = [] r_down = [] for k in range(len(theta_up)): r_up = r_up + [ N * lambda_0 / (self.n_e * (1 - e * cos(float(theta_up[k])))) + j * self.period + 2 * spacing + wl ] r_down = r_up[::-1] xr = [] yr = [] for k in range(len(theta_up)): xr = xr + [r_up[k] * cos(theta_up[k])] yr = yr + [r_up[k] * sin(theta_up[k])] xl = [] yl = [] for k in range(len(theta_down)): xl = xl + [(r_down[k] + wh) * cos(theta_down[k])] yl = yl + [(r_down[k] + wh) * sin(theta_down[k])] x = xr + xl y = yr + yl pts = [] for i in range(len(x)): pts.append(Point.from_dpoint(DPoint(x[i] / dbu, y[i] / dbu))) polygon = Polygon(pts) shapes(LayerSiN).insert(polygon) # Taper section r_up = [] r_down = [] for k in range(len(theta_up)): r_up = r_up + [ N * lambda_0 / (self.n_e * (1 - e * cos(float(theta_up[k])))) ] r_down = r_up[::-1] xl = [] yl = [] for k in range(len(theta_down)): xl = xl + [(r_down[k]) * cos(theta_down[k])] yl = yl + [(r_down[k]) * sin(theta_down[k])] yr = [self.t / 2., self.t / 2., -self.t / 2., -self.t / 2.] yl_abs = [] for k in range(len(yl)): yl_abs = yl_abs + [abs(yl[k])] y_max = max(yl_abs) iy_max = yl_abs.index(y_max) L_o = (y_max - self.t / 2) / tan((pi / 180) * self.angle_e / 2) xr = [L_o + xl[iy_max], 0, 0, L_o + xl[iy_max]] x = xr + xl y = yr + yl pts = [] for i in range(len(x)): pts.append(Point.from_dpoint(DPoint(x[i] / dbu, y[i] / dbu))) polygon = Polygon(pts) shapes(LayerSiN).insert(polygon) # Pin on the waveguide: from SiEPIC._globals import PIN_LENGTH as pin_length x = 0 t = Trans(Trans.R0, x, 0) pin = Path([Point(-pin_length / 2, 0), Point(pin_length / 2, 0)], self.t / dbu) pin_t = pin.transformed(t) shapes(LayerPinRecN).insert(pin_t) text = Text("pin1", t) shape = shapes(LayerPinRecN).insert(text) shape.text_size = 0.4 / dbu # Device recognition layer yr = sin(start) * (N * lambda_0 / (self.n_e * (1 - e * cos(float(start)))) + gc_number * self.period + spacing) box1 = Box( -(self.grating_length + self.taper_length) / dbu - pin_length * 2, yr / dbu, 0, -yr / dbu) shapes(LayerDevRecN).insert(box1)
def produce_impl(self): # fetch the parameters dbu = self.layout.dbu ly = self.layout shapes = self.cell.shapes LayerSi = self.layer LayerSiN = ly.layer(LayerSi) LayerPinRecN = ly.layer(self.pinrec) LayerDevRecN = ly.layer(self.devrec) # draw spiral from math import pi, cos, sin, log, sqrt # Archimedes spiral # r = b + a * theta b = self.min_radius spacing = self.wg_spacing + self.wg_width a = 2 * spacing / (2 * pi) # area, length, turn tracking for spiral area = 0 spiral_length = 0 turn = -1 from SiEPIC.utils import points_per_circle, arc_wg_xy while spiral_length < self.length: turn += 1 # Spiral #1 pts = [] # local radius: r = 2 * b + a * turn * 2 * pi - self.wg_width / 2 # number of points per circle: npoints = int(points_per_circle(r)) # increment, in radians, for each point: da = 2 * pi / npoints # draw the inside edge of spiral for i in range(0, npoints + 1): t = i * da xa = (a * t + r) * cos(t) ya = (a * t + r) * sin(t) pts.append(Point.from_dpoint(DPoint(xa / dbu, ya / dbu))) # draw the outside edge of spiral r = 2 * b + a * turn * 2 * pi + self.wg_width / 2 npoints = int(points_per_circle(r)) da = 2 * pi / npoints for i in range(npoints, -1, -1): t = i * da xa = (a * t + r) * cos(t) ya = (a * t + r) * sin(t) pts.append(Point.from_dpoint(DPoint(xa / dbu, ya / dbu))) polygon = Polygon(pts) area += polygon.area() shapes(LayerSiN).insert(polygon) # Spiral #2 pts = [] # local radius: r = 2 * b + a * turn * 2 * pi - self.wg_width / 2 - spacing # number of points per circle: npoints = int(points_per_circle(r)) # increment, in radians, for each point: da = 2 * pi / npoints # draw the inside edge of spiral for i in range(0, npoints + 1): t = i * da + pi xa = (a * t + r) * cos(t) ya = (a * t + r) * sin(t) pts.append(Point.from_dpoint(DPoint(xa / dbu, ya / dbu))) # draw the outside edge of spiral r = 2 * b + a * turn * 2 * pi + self.wg_width / 2 - spacing npoints = int(points_per_circle(r)) da = 2 * pi / npoints for i in range(npoints, -1, -1): t = i * da + pi xa = (a * t + r) * cos(t) ya = (a * t + r) * sin(t) pts.append(Point.from_dpoint(DPoint(xa / dbu, ya / dbu))) polygon = Polygon(pts) area += polygon.area() shapes(LayerSiN).insert(polygon) # waveguide length: spiral_length = area / self.wg_width * dbu * dbu + 2 * pi * self.min_radius if self.spiral_ports: # Spiral #1 extra 1/2 arm turn = turn + 1 pts = [] # local radius: r = 2 * b + a * turn * 2 * pi - self.wg_width / 2 # number of points per circle: npoints = int(points_per_circle(r)) # increment, in radians, for each point: da = pi / npoints # draw the inside edge of spiral for i in range(0, npoints + 1): t = i * da xa = (a * t + r) * cos(t) ya = (a * t + r) * sin(t) pts.append(Point.from_dpoint(DPoint(xa / dbu, ya / dbu))) # draw the outside edge of spiral r = 2 * b + a * turn * 2 * pi + self.wg_width / 2 npoints = int(points_per_circle(r)) da = pi / npoints for i in range(npoints, -1, -1): t = i * da xa = (a * t + r) * cos(t) ya = (a * t + r) * sin(t) pts.append(Point.from_dpoint(DPoint(xa / dbu, ya / dbu))) polygon = Polygon(pts) area += polygon.area() shapes(LayerSiN).insert(polygon) turn = turn - 1 # waveguide length: spiral_length = area / self.wg_width * dbu * dbu + 2 * pi * self.min_radius # Centre S-shape connecting waveguide #layout_arc_wg_dbu(self.cell, LayerSiN, -b/dbu, 0, b/dbu, self.wg_width/dbu, 0, 180) self.cell.shapes(LayerSiN).insert( arc_wg_xy(-b / dbu, 0, b / dbu, self.wg_width / dbu, 0, 180)) #layout_arc_wg_dbu(self.cell, LayerSiN, b/dbu, 0, b/dbu, self.wg_width/dbu, 180, 0) self.cell.shapes(LayerSiN).insert( arc_wg_xy(b / dbu, 0, b / dbu, self.wg_width / dbu, 180, 0)) print("spiral length: %s microns" % spiral_length) # Pins on the waveguide: from SiEPIC._globals import PIN_LENGTH as pin_length x = -(2 * b + a * (turn + 1) * 2 * pi) / dbu w = self.wg_width / dbu t = Trans(Trans.R0, x, 0) pin = Path([Point(0, pin_length / 2), Point(0, -pin_length / 2)], w) pin_t = pin.transformed(t) shapes(LayerPinRecN).insert(pin_t) text = Text("pin2", t) shape = shapes(LayerPinRecN).insert(text) shape.text_size = 0.4 / dbu if self.spiral_ports: x = -(2 * b + a * (turn + 1.5) * 2 * pi) / dbu else: x = (2 * b + a * (turn + 1) * 2 * pi) / dbu t = Trans(Trans.R0, x, 0) pin = Path([Point(0, pin_length / 2), Point(0, -pin_length / 2)], w) pin_t = pin.transformed(t) shapes(LayerPinRecN).insert(pin_t) text = Text("pin1", t) shape = shapes(LayerPinRecN).insert(text) shape.text_size = 0.4 / dbu # Compact model information t = Trans(Trans.R0, -abs(x), 0) text = Text('Length=%.3fu' % spiral_length, t) shape = shapes(LayerDevRecN).insert(text) shape.text_size = abs(x) / 8 t = Trans(Trans.R0, 0, 0) text = Text('Lumerical_INTERCONNECT_library=Design kits/ebeam_v1.2', t) shape = shapes(LayerDevRecN).insert(text) shape.text_size = 0.1 / dbu t = Trans(Trans.R0, 0, w * 2) text = Text('Component=ebeam_wg_strip_1550', t) shape = shapes(LayerDevRecN).insert(text) shape.text_size = 0.1 / dbu t = Trans(Trans.R0, 0, -w * 2) text = Text \ ('Spice_param:wg_length=%.3fu wg_width=%.3fu min_radius=%.3fu wg_spacing=%.3fu' %\ (spiral_length, self.wg_width, (self.min_radius), self.wg_spacing), t ) shape = shapes(LayerDevRecN).insert(text) shape.text_size = 0.1 / dbu # Create the device recognition layer -- make it 1 * wg_width away from the waveguides. x = abs(x) npoints = int(points_per_circle(x) / 10) da = 2 * pi / npoints # increment, in radians r = x + 2 * self.wg_width / dbu pts = [] for i in range(0, npoints + 1): pts.append( Point.from_dpoint(DPoint(r * cos(i * da), r * sin(i * da)))) shapes(LayerDevRecN).insert(Polygon(pts)) print("spiral done.")
def produce_impl(self): # This is the main part of the implementation: create the layout # fetch the parameters dbu = self.layout.dbu ly = self.layout shapes = self.cell.shapes LayerSi = self.silayer LayerSiN = ly.layer(LayerSi) TextLayerN = ly.layer(self.textl) LayerPinRecN = ly.layer(self.pinrec) LayerDevRecN = ly.layer(self.devrec) from SiEPIC.utils import points_per_circle # Create the ring resonator: layout_Ring(self.cell, LayerSiN, self.r + self.w / 2, self.r + self.g + self.w, self.r, self.w, points_per_circle(self.r)) w = int(round(self.w / dbu)) r = int(round(self.r / dbu)) g = int(round(self.g / dbu)) # pcell = ly.create_cell("DirectionalCoupler_HalfRing_Straight", "SiEPIC", { "r": self.r, "w": self.w, "g": self.g, "silayer": LayerSi, "bustype": 0 } ) # print ("Cell: pcell: #%s" % pcell.cell_index()) # t = Trans(Trans.R0, 0, 0) # instance = self.cell.insert(CellInstArray(pcell.cell_index(), t)) # t = Trans(Trans.R180, 0, 2*r+2*g+2*w) # instance = self.cell.insert(CellInstArray(pcell.cell_index(), t)) # Create the two waveguides wg1 = Box(0, -w / 2, w + 2 * r, w / 2) shapes(LayerSiN).insert(wg1) y_offset = 2 * r + 2 * g + 2 * w wg2 = Box(0, y_offset - w / 2, w + 2 * r, y_offset + w / 2) shapes(LayerSiN).insert(wg2) from SiEPIC._globals import PIN_LENGTH as pin_length # Create the pins, as short paths: pin = Path([Point(pin_length / 2, 0), Point(-pin_length / 2, 0)], w) shapes(LayerPinRecN).insert(pin) t = Trans(Trans.R0, 0, 0) text = Text("pin1", t) shape = shapes(LayerPinRecN).insert(text) shape.text_size = 0.4 / dbu pin = Path([ Point(w + 2 * r - pin_length / 2, 0), Point(w + 2 * r + pin_length / 2, 0) ], w) shapes(LayerPinRecN).insert(pin) t = Trans(Trans.R0, w + 2 * r, 0) text = Text("pin2", t) shape = shapes(LayerPinRecN).insert(text) shape.text_size = 0.4 / dbu pin = Path([ Point(pin_length / 2, y_offset), Point(-pin_length / 2, y_offset) ], w) shapes(LayerPinRecN).insert(pin) t = Trans(Trans.R0, 0, y_offset) text = Text("pin3", t) shape = shapes(LayerPinRecN).insert(text) shape.text_size = 0.4 / dbu pin = Path([ Point(w + 2 * r - pin_length / 2, y_offset), Point(w + 2 * r + pin_length / 2, y_offset) ], w) shapes(LayerPinRecN).insert(pin) t = Trans(Trans.R0, w + 2 * r, y_offset) text = Text("pin4", t) shape = shapes(LayerPinRecN).insert(text) shape.text_size = 0.4 / dbu # Create the device recognition layer dev = Box(0, -w * 3, w + 2 * r, y_offset + w * 3) shapes(LayerDevRecN).insert(dev) # Add a polygon text description if self.textpolygon: layout_pgtext(self.cell, self.textl, self.w, self.r + self.w, "%.3f-%g" % (self.r, self.g), 1) print("Done drawing the layout for - DoubleBus_Ring: %.3f-%g" % (self.r, self.g))
def produce_impl(self): # fetch the parameters dbu = self.layout.dbu ly = self.layout shapes = self.cell.shapes LayerSi = self.layer LayerSiN = ly.layer(self.layer) LayerPinRecN = ly.layer(self.pinrec) LayerDevRecN = ly.layer(self.devrec) LayerTextN = ly.layer(self.textl) from math import pi, cos, sin, log, sqrt, tan lambda_0 = self.wavelength ##um wavelength of light pin_length = 0.5 ##um extra nub for the waveguid attachment # Geometry wh = self.period * self.dc ##thick grating wl = self.ff * (self.period - wh) ## thin grating spacing = (self.period - wh - wl) / 2 ##space between thick and thin gc_number = int(round(self.grating_length / self.period)) ##number of periods gc_number = 3 e = self.n_t * sin((pi / 180) * self.theta_c) / self.n_e N = round(self.taper_length * (1 + e) * self.n_e / lambda_0) ##allows room for the taper start = (pi - (pi / 180) * self.angle_e / 2) stop = (pi + (pi / 180) * self.angle_e / 2) # Draw coupler grating. for j in range(gc_number): # number of points in the arcs: # calculate such that the vertex & edge placement error is < 0.5 nm. # see "SiEPIC_EBeam_functions - points_per_circle" for more details radius = N * lambda_0 / (self.n_e * (1 - e)) + j * self.period + spacing seg_points = int( points_per_circle(radius / dbu) / 360. * self.angle_e) # number of points grating arc theta_up = [] for m in range(seg_points + 1): theta_up = theta_up + [start + m * (stop - start) / seg_points] theta_down = theta_up[::-1] ##small one r_up = [] r_down = [] rng = range(len(theta_up)) # find the divider to get desired fab error: th = min(theta_up) div = (2 * sin(th) / self.fab_error) * (N * lambda_0 / (self.n_e * (1 - e * cos(th))) + j * self.period + spacing) err = (2 * sin(th) / div) * (N * lambda_0 / (self.n_e * (1 - e * cos(th))) + j * self.period + spacing) # print("div %s, err (double check) %s" % (div, err)) for k in rng: th = theta_up[k] # print("%s, %s, %s" % (th, sin(th), 1+sin(th)/10.) ) r_up = r_up + [ (1 - sin(th) / div) * (N * lambda_0 / (self.n_e * (1 - e * cos(th))) + j * self.period + spacing) ] for k in rng[::-1]: th = theta_up[k] # print("%s, %s, %s" % (th, sin(th), 1+sin(th)/10.) ) r_down = r_down + [ (1 + sin(th) / div) * (N * lambda_0 / (self.n_e * (1 - e * cos(th))) + j * self.period + spacing) ] xr = [] yr = [] for k in range(len(theta_up)): xr = xr + [r_up[k] * cos(theta_up[k])] yr = yr + [r_up[k] * sin(theta_up[k])] xl = [] yl = [] for k in range(len(theta_down)): xl = xl + [(r_down[k] + wl) * cos(theta_down[k])] yl = yl + [(r_down[k] + wl) * sin(theta_down[k])] x = xr + xl y = yr + yl pts = [] for i in range(len(x)): pts.append( Point.from_dpoint(pya.DPoint(x[i] / dbu, y[i] / dbu))) #small_one = core.Boundary(points) polygon = Polygon(pts) shapes(LayerSiN).insert(polygon) if j == 1: # text label dimensions, for minor grating: # top shapes(LayerTextN).insert( Text("%0.0f" % ((wl + self.fab_error) * 1000), Trans(Trans.R0, xl[0] / dbu, yl[0] / dbu))).text_size = 0.2 / dbu # btm shapes(LayerTextN).insert( Text("%0.0f" % ((wl - self.fab_error) * 1000), Trans(Trans.R0, xl[-1] / dbu, yl[-1] / dbu))).text_size = 0.2 / dbu # mid shapes(LayerTextN).insert( Text( "%0.0f" % ((wl) * 1000), Trans(Trans.R0, xl[int(len(theta_up) / 2)] / dbu, yl[int(len(theta_up) / 2)] / dbu))).text_size = 0.2 / dbu ##big one r_up = [] r_down = [] # find the divider to get desired fab error: th = min(theta_up) div = (2 * sin(th) / self.fab_error) * ( N * lambda_0 / (self.n_e * (1 - e * cos(th))) + j * self.period + 2 * spacing + wl) err = (2 * sin(th) / div) * (N * lambda_0 / (self.n_e * (1 - e * cos(th))) + j * self.period + 2 * spacing + wl) # print("div %s, err (double check) %s" % (div, err)) rng = range(len(theta_up)) for k in rng: th = theta_up[k] r_up = r_up + [ (1 - sin(th) / div) * (N * lambda_0 / (self.n_e * (1 - e * cos(th))) + j * self.period + 2 * spacing + wl) ] for k in rng[::-1]: th = theta_up[k] r_down = r_down + [ (1 + sin(th) / div) * (N * lambda_0 / (self.n_e * (1 - e * cos(th))) + j * self.period + 2 * spacing + wl) ] xr = [] yr = [] for k in range(len(theta_up)): xr = xr + [r_up[k] * cos(theta_up[k])] yr = yr + [r_up[k] * sin(theta_up[k])] xl = [] yl = [] for k in range(len(theta_down)): xl = xl + [(r_down[k] + wh) * cos(theta_down[k])] yl = yl + [(r_down[k] + wh) * sin(theta_down[k])] x = xr + xl y = yr + yl pts = [] for i in range(len(x)): pts.append( Point.from_dpoint(pya.DPoint(x[i] / dbu, y[i] / dbu))) polygon = Polygon(pts) shapes(LayerSiN).insert(polygon) if j == 1: # text label dimensions, for major grating: # top shapes(LayerTextN).insert( Text("%0.0f" % ((wh + self.fab_error) * 1000), Trans(Trans.R0, xl[0] / dbu, yl[0] / dbu))).text_size = 0.2 / dbu # btm shapes(LayerTextN).insert( Text("%0.0f" % ((wh - self.fab_error) * 1000), Trans(Trans.R0, xl[-1] / dbu, yl[-1] / dbu))).text_size = 0.2 / dbu # mid shapes(LayerTextN).insert( Text( "%0.0f" % ((wh) * 1000), Trans(Trans.R0, xl[int(len(theta_up) / 2)] / dbu, yl[int(len(theta_up) / 2)] / dbu))).text_size = 0.2 / dbu
def produce_impl(self): # fetch the parameters dbu = self.layout.dbu ly = self.layout shapes = self.cell.shapes LayerSi = self.layer LayerSiN = ly.layer(self.layer) LayerPinRecN = ly.layer(self.pinrec) LayerDevRecN = ly.layer(self.devrec) LayerTextN = ly.layer(self.textl) from math import pi, cos, sin, log, sqrt, tan lambda_0 = self.wavelength ##um wavelength of light pin_length = 0.5 ##um extra nub for the waveguide attachment # Geometry wh = self.period * self.dc ##thick grating wl = self.ff * (self.period - wh) ## thin grating # Width scale parameter is a first pass attempt at designing for length contraction # at cryogenic temperature. It is applied BEFORE the width error; this is because # the order of operations in the reverse is over/under-etch, then cool and contract. # So first scale so that target width is reached after contraction, then add # fabrication error so that the scaled width is reached. wh = (wh * self.w_scale + self.w_err) wl = (wl * self.w_scale + self.w_err) spacing = (self.period - wh - wl) / 2 ##space between thick and thin gc_number = int(round(self.grating_length / self.period)) ##number of periods e = self.n_t * sin((pi / 180) * self.theta_c) / self.n_e N = round(self.taper_length * (1 + e) * self.n_e / lambda_0) ##allows room for the taper start = (pi - (pi / 180) * self.angle_e / 2) stop = (pi + (pi / 180) * self.angle_e / 2) # Draw coupler grating. for j in range(gc_number): # number of points in the arcs: # calculate such that the vertex & edge placement error is < 0.5 nm. # see "SiEPIC_EBeam_functions - points_per_circle" for more details radius = N * lambda_0 / (self.n_e * (1 - e)) + j * self.period + spacing seg_points = int( points_per_circle(radius / dbu) / 360. * self.angle_e) # number of points grating arc theta_up = [] for m in range(seg_points + 1): theta_up = theta_up + [start + m * (stop - start) / seg_points] theta_down = theta_up[::-1] ##small one r_up = [] r_down = [] for k in range(len(theta_up)): r_up = r_up + [ N * lambda_0 / (self.n_e * (1 - e * cos(float(theta_up[k])))) + j * self.period + spacing ] r_down = r_up[::-1] xr = [] yr = [] for k in range(len(theta_up)): xr = xr + [r_up[k] * cos(theta_up[k])] yr = yr + [r_up[k] * sin(theta_up[k])] xl = [] yl = [] for k in range(len(theta_down)): xl = xl + [(r_down[k] + wl) * cos(theta_down[k])] yl = yl + [(r_down[k] + wl) * sin(theta_down[k])] x = xr + xl y = yr + yl pts = [] for i in range(len(x)): pts.append( Point.from_dpoint(pya.DPoint(x[i] / dbu, y[i] / dbu))) #small_one = core.Boundary(points) polygon = Polygon(pts) shapes(LayerSiN).insert(polygon) ##big one r_up = [] r_down = [] for k in range(len(theta_up)): r_up = r_up + [ N * lambda_0 / (self.n_e * (1 - e * cos(float(theta_up[k])))) + j * self.period + 2 * spacing + wl ] r_down = r_up[::-1] xr = [] yr = [] for k in range(len(theta_up)): xr = xr + [r_up[k] * cos(theta_up[k])] yr = yr + [r_up[k] * sin(theta_up[k])] xl = [] yl = [] for k in range(len(theta_down)): xl = xl + [(r_down[k] + wh) * cos(theta_down[k])] yl = yl + [(r_down[k] + wh) * sin(theta_down[k])] x = xr + xl y = yr + yl pts = [] for i in range(len(x)): pts.append( Point.from_dpoint(pya.DPoint(x[i] / dbu, y[i] / dbu))) polygon = Polygon(pts) shapes(LayerSiN).insert(polygon) # Taper section r_up = [] r_down = [] for k in range(len(theta_up)): r_up = r_up + [ N * lambda_0 / (self.n_e * (1 - e * cos(float(theta_up[k])))) ] r_down = r_up[::-1] xl = [] yl = [] for k in range(len(theta_down)): xl = xl + [(r_down[k]) * cos(theta_down[k])] yl = yl + [(r_down[k]) * sin(theta_down[k])] yr = [self.t / 2., self.t / 2., -self.t / 2., -self.t / 2.] yl_abs = [] for k in range(len(yl)): yl_abs = yl_abs + [abs(yl[k])] y_max = max(yl_abs) iy_max = yl_abs.index(y_max) L_o = (y_max - self.t / 2) / tan((pi / 180) * self.angle_e / 2) xr = [L_o + xl[iy_max], 0, 0, L_o + xl[iy_max]] x = xr + xl y = yr + yl pts = [] for i in range(len(x)): pts.append(Point.from_dpoint(pya.DPoint(x[i] / dbu, y[i] / dbu))) polygon = Polygon(pts) shapes(LayerSiN).insert(polygon) # Pin on the waveguide: pin_length = 200 x = 0 t = Trans(x, 0) pin = pya.Path([Point(-pin_length / 2, 0), Point(pin_length / 2, 0)], self.t / dbu) pin_t = pin.transformed(t) shapes(LayerPinRecN).insert(pin_t) text = Text("pin1", t) shape = shapes(LayerPinRecN).insert(text) shape.text_size = 0.4 / dbu # Device recognition layer yr = sin(start) * (N * lambda_0 / (self.n_e * (1 - e * cos(float(start)))) + gc_number * self.period + spacing) box1 = Box( -(self.grating_length + self.taper_length) / dbu - pin_length * 2, yr / dbu, 0, -yr / dbu) shapes(LayerDevRecN).insert(box1)