def __build_cell(self): # Sequentially build all the geometric shapes using gdspy path functions # then add it to the Cell num_teeth = int(self.length // self.period) """ Create a straight grating GratingCoupler """ gap = self.period - (self.period * self.dc) path = gdspy.Path(self.wgt.wg_width, (0, 0)) path.segment(self.taper_length, direction="+x", final_width=self.width, **self.wg_spec) teeth = gdspy.L1Path(( gap + self.taper_length + 0.5 * (num_teeth - 1 + self.dc) * self.period, -0.5 * self.width, ), "+y", self.period * self.dc, [self.width], [], num_teeth, self.period, **self.wg_spec) clad_path = gdspy.Path(self.wgt.wg_width + 2 * self.wgt.clad_width, (0, 0)) clad_path.segment(self.taper_length, direction="+x", final_width=self.width + 2 * self.wgt.clad_width, **self.clad_spec) clad_path.segment(self.length, direction="+x", **self.clad_spec) self.add(teeth) self.add(path) self.add(clad_path)
def build_cell(self): #Sequentially build all the geometric shapes using gdspy path functions #then add it to the Cell num_teeth = int(self.length // self.period) """ Create a straight grating GratingCoupler """ gap = self.period - (self.period * self.dc) path = gdspy.Path(self.wgt.wg_width, self.port) path.segment(self.taper_length, direction='+y', final_width=self.width, **self.wg_spec) teeth = gdspy.L1Path( (self.port[0] - 0.5 * self.width, gap + self.taper_length + self.port[1] + 0.5 * (num_teeth - 1 + self.dc) * self.period), '+x', self.period * self.dc, [self.width], [], num_teeth, self.period, **self.wg_spec) clad_path = gdspy.Path(self.wgt.wg_width + 2 * self.wgt.clad_width, self.port) clad_path.segment(self.taper_length, direction='+y', final_width=self.width + 2 * self.wgt.clad_width, **self.clad_spec) clad_path.segment(self.length, direction='+y', **self.clad_spec) if self.direction == "WEST": teeth.rotate(np.pi / 2.0, self.port) path.rotate(np.pi / 2.0, self.port) clad_path.rotate(np.pi / 2.0, self.port) elif self.direction == "SOUTH": teeth.rotate(np.pi, self.port) path.rotate(np.pi, self.port) clad_path.rotate(np.pi, self.port) elif self.direction == "EAST": teeth.rotate(-np.pi / 2.0, self.port) path.rotate(-np.pi / 2.0, self.port) clad_path.rotate(-np.pi / 2.0, self.port) elif isinstance(self.direction, float): teeth.rotate(self.direction - np.pi / 2.0, self.port) path.rotate(self.direction - np.pi / 2.0, self.port) clad_path.rotate(self.direction - np.pi / 2.0, self.port) self.add(teeth) self.add(path) self.add(clad_path)
# method. Here we use a radius of 0.2. # polypath.fillet(0.2) path_cell.add(polypath) # L1Paths use only segments in 'x' and 'y' directions, useful for some # lithography mask writers. We specify a path composed of 16 segments # of length 4. The turns after each segment can be either 90 degrees # CCW (positive) or CW (negative). The absolute value of the turns # produces a scaling of the path width and distance between paths in # segments immediately after the turn. lengths = [4] * 8 turns = [-1, -1, 1, 1, -1, -2, 1, 0.5] l1path = gdspy.L1Path((-1, -11), '+y', 0.5, lengths, turns, number_of_paths=3, distance=0.7, layer=6) path_cell.add(l1path) # ------------------------------------------------------------------ # # POLYGON OPERATIONS # ------------------------------------------------------------------ # # Boolean operations can be executed with either gdspy polygons or # point lists). The operations are union, intersection, subtraction, # symmetric subtracion (respectively 'or', 'and', 'not', 'xor'). oper_cell = gdspy.Cell('OPERATIONS') # Here we subtract the previously created spiral from a rectangle with
def grating( period, number_of_teeth, fill_frac, width, position, direction, lda=1, sin_theta=0, focus_distance=-1, focus_width=-1, tolerance=0.001, layer=0, datatype=0, ): """ Straight or focusing grating. period : grating period number_of_teeth : number of teeth in the grating fill_frac : filling fraction of the teeth (w.r.t. the period) width : width of the grating position : grating position (feed point) direction : one of {'+x', '-x', '+y', '-y'} lda : free-space wavelength sin_theta : sine of incidence angle focus_distance : focus distance (negative for straight grating) focus_width : if non-negative, the focusing area is included in the result (usually for negative resists) and this is the width of the waveguide connecting to the grating tolerance : same as in `path.parametric` layer : GDSII layer number datatype : GDSII datatype number Return `PolygonSet` """ if focus_distance < 0: p = gdspy.L1Path( ( position[0] - 0.5 * width, position[1] + 0.5 * (number_of_teeth - 1 + fill_frac) * period, ), "+x", period * fill_frac, [width], [], number_of_teeth, period, layer=layer, datatype=datatype, ) else: neff = lda / float(period) + sin_theta qmin = int(focus_distance / float(period) + 0.5) p = gdspy.Path(period * fill_frac, position) c3 = neff ** 2 - sin_theta ** 2 w = 0.5 * width for q in range(qmin, qmin + number_of_teeth): c1 = q * lda * sin_theta c2 = (q * lda) ** 2 p.parametric( lambda t: ( width * t - w, (c1 + neff * numpy.sqrt(c2 - c3 * (width * t - w) ** 2)) / c3, ), tolerance=tolerance, max_points=0, layer=layer, datatype=datatype, ) p.x = position[0] p.y = position[1] sz = p.polygons[0].shape[0] // 2 if focus_width == 0: p.polygons[0] = numpy.vstack((p.polygons[0][:sz, :], [position])) elif focus_width > 0: p.polygons[0] = numpy.vstack( ( p.polygons[0][:sz, :], [ (position[0] + 0.5 * focus_width, position[1]), (position[0] - 0.5 * focus_width, position[1]), ], ) ) p.fracture() if direction == "-x": return p.rotate(0.5 * numpy.pi, position) elif direction == "+x": return p.rotate(-0.5 * numpy.pi, position) elif direction == "-y": return p.rotate(numpy.pi, position) else: return p
# of segments for our bend radius, we'll first route the waveguide # down towards the bottom waveguide, and then back up...just to be safe. stretch = chipDim - k * couplePitch + RingcellWidth / 2 sub1 = taperWidth travel = couplePitch - middleTapperHeightBuffer sub2 = stretch - sub1 length = [ sub1, travel, sub2, travel + couplePitch - RingcellHeight + waveguideWidth, couplerBufferMiddle + 1 ] turn = [1, -1, -1, -1] l1path = gdspy.L1Path( initial_point=(chipDim - taperWidth, -(k + 1) * couplePitch - couplerYOffset), direction='-x', width=waveguideWidth, length=length, turn=turn, layer=layerNumber) l1path.fillet(radius=waveguideBendRadius) RingUnitCell.add(l1path) # consolidate cells to master cell RRLatticeCell.add(gdspy.CellReference(RingUnitCell)) incrementOffset = incrementOffset + 1 # ------------------------------------------------------------------ # # Center Parent Cell for fab # ------------------------------------------------------------------ # centeredCell = gdspy.Cell('centeredCell')
def grating(period, number_of_teeth, fill_frac, width, position, direction, lda=1, sin_theta=0, focus_distance=-1, focus_width=-1, evaluations=99, layer=0, datatype=0): ''' Straight or focusing grating. period : grating period number_of_teeth : number of teeth in the grating fill_frac : filling fraction of the teeth (w.r.t. the period) width : width of the grating position : grating position (feed point) direction : one of {'+x', '-x', '+y', '-y'} lda : free-space wavelength sin_theta : sine of incidence angle focus_distance : focus distance (negative for straight grating) focus_width : if non-negative, the focusing area is included in the result (usually for negative resists) and this is the width of the waveguide connecting to the grating evaluations : number of evaluations of `path.parametric` layer : GDSII layer number datatype : GDSII datatype number Return `PolygonSet` ''' if focus_distance < 0: path = gdspy.L1Path((position[0] - 0.5 * width, position[1] + 0.5 * (number_of_teeth - 1 + fill_frac) * period), '+x', period * fill_frac, [width], [], number_of_teeth, period, layer=layer, datatype=datatype) else: neff = lda / float(period) + sin_theta qmin = int(focus_distance / float(period) + 0.5) path = gdspy.Path(period * fill_frac, position) max_points = 199 if focus_width < 0 else 2 * evaluations c3 = neff**2 - sin_theta**2 w = 0.5 * width for q in range(qmin, qmin + number_of_teeth): c1 = q * lda * sin_theta c2 = (q * lda)**2 path.parametric( lambda t: (width * t - w, (c1 + neff * numpy.sqrt(c2 - c3 * (width * t - w)**2)) / c3), number_of_evaluations=evaluations, max_points=max_points, layer=layer, datatype=datatype) path.x = position[0] path.y = position[1] if focus_width >= 0: path.polygons[0] = numpy.vstack( (path.polygons[0][:evaluations, :], ([position] if focus_width == 0 else [(position[0] + 0.5 * focus_width, position[1]), (position[0] - 0.5 * focus_width, position[1])]))) path.fracture() if direction == '-x': return path.rotate(0.5 * numpy.pi, position) elif direction == '+x': return path.rotate(-0.5 * numpy.pi, position) elif direction == '-y': return path.rotate(numpy.pi, position) else: return path
[taperWidth, -k * couplePitch + waveguideWidth / 2], [ k * couplePitch - MZIcellWidth / 2, -k * couplePitch - waveguideWidth / 2 ], layer=layerNumber)) # connect middle taper to coupler length = [ k * couplePitch - taperWidth + MZIcellWidth / 2 + couplerBufferMiddle, couplePitch, couplerBufferMiddle + 1 ] turn = [1, 1] l1path = gdspy.L1Path(initial_point=(taperWidth, -(k + 1) * couplePitch), direction='+x', width=waveguideWidth, length=length, turn=turn, layer=layerNumber) l1path.fillet(radius=waveguideBendRadius) MZIUnitCell.add(l1path) # connect bottom taper to coupler length = [ k * couplePitch - taperWidth + MZIcellWidth / 2 + couplerBufferBottom, 2 * couplePitch + 2 * yOffset - waveguideWidth, couplerBufferBottom + 1 ] turn = [1, 1] l2path = gdspy.L1Path(initial_point=(taperWidth,
def MZI(deltaL=40, Lref=40, gapLength=20, waveguideWidth=0.5, bendRadius=5, coupleType="Y", polarization="TM", layerNumber=1, maxH=100): # Decide which coupler to use if coupleType == "Y": Coupler = YBranch() elif coupleType == "C": Coupler = branchCoupler(layerNumber=1, polarization=polarization) else: raise Exception('Invalid Coupler Type Specified; must be Y or C') # Calculate how many, and how long each leg of the coupling arm needs to be numLegs = 2 if deltaL / 2 > maxH: numLegs = numpy.floor((deltaL) / maxH + 1) if numLegs % 2 == 1: numLegs = numLegs + 1 leg = (deltaL / numLegs) + (4 * bendRadius - numpy.pi * bendRadius ) # compensate with half circumf of circle # Connect bottom branch bottomBranch = gdspy.Cell('referenceBranch_' + (coupleType), exclude_from_current=True) bottomBranch.add( gdspy.Rectangle([-Lref / 2, waveguideWidth / 2], [Lref / 2, -waveguideWidth / 2], layer=1)) # Connect top branch gapLength = Lref / (numLegs + 1) #outerArm = (Lref-gapLength)/2 basicTurn = [1, -1, -1, 1] basicLength = [gapLength, leg, gapLength, leg] turn = [] length = [] for k in range(0, int(numLegs / 2)): length.extend(basicLength) turn.extend(basicTurn) length.append(gapLength) topBranch = gdspy.Cell('deltaBranch_' + (coupleType), exclude_from_current=True) topBranchPoly = gdspy.L1Path((0, 0), '+x', waveguideWidth, length, turn, layer=1) topBranchPoly.fillet(radius=bendRadius) leftSquare = gdspy.Rectangle([0, -waveguideWidth / 2], [waveguideWidth, waveguideWidth / 2], layer=1) rightSquare = gdspy.Rectangle([Lref - waveguideWidth, -waveguideWidth / 2], [Lref, waveguideWidth / 2], layer=1) union = gdspy.fast_boolean(topBranchPoly, [leftSquare, rightSquare], 'or', layer=layerNumber) topBranch.add(union) # Get Branch dimensions CouplerDims = Coupler.get_bounding_box() CouplerWidth = abs(CouplerDims[0, 0] - CouplerDims[1, 0]) CouplerHeight = abs(CouplerDims[0, 1] - CouplerDims[1, 1]) # Since the coupler isn't centered, let's compensate for the later translation if coupleType == "Y": CouplerWidth = 2 * CouplerWidth MZIcell = gdspy.Cell('MZI_deltaL=' + str(deltaL) + '_Lref=' + str(Lref) + '_coupleType=' + coupleType) MZIcell.add(gdspy.CellReference(Coupler, (-Lref / 2 - CouplerWidth / 2, 0))) MZIcell.add( gdspy.CellReference(Coupler, (Lref / 2 + CouplerWidth / 2, 0), rotation=180)) MZIcell.add( gdspy.CellReference(bottomBranch, (0, -(CouplerHeight / 2 - waveguideWidth / 2.0)))) MZIcell.add( gdspy.CellReference( topBranch, (-Lref / 2, +(CouplerHeight / 2 - waveguideWidth / 2.0)))) MZIcell.flatten() return MZIcell