diff --git a/picwriter/components/waveguide.py b/picwriter/components/waveguide.py index 91f4747..291472c 100644 --- a/picwriter/components/waveguide.py +++ b/picwriter/components/waveguide.py @@ -4,6 +4,7 @@ import numpy as np import gdspy import picwriter.toolkit as tk +from picwriter.components.ebend import EBend class WaveguideTemplate: """ Template for waveguides that contains standard information about the geometry and fabrication. Supported waveguide types are **strip** (also known as 'channel' waveguides), **slot**, and **SWG** ('sub-wavelength grating', or 1D photonic crystal waveguides). @@ -13,6 +14,7 @@ class WaveguideTemplate: * **bend_radius** (float): Radius of curvature for waveguide bends (circular). Defaults to 50. * **waveguide_stack** (list): List of layers and path widths to be drawn when waveguides are routed & placed. Format is '[[width1, (layer1, datatype1)], [width2, (layer2, datatype2)], ...]'. The first element defines the main waveguide width & layer for slot and subwavelength gratings. If using waveguide_stack, the following keyword arguments are ignored: wg_width, clad_width, wg_layer, wg_datatype, clad_layer, clad_datatype. Defaults to [[2.0, (1,0)], [10.0, (2,0)]]. * **wg_width** (float): Width of the waveguide as shown on the mask. Defaults to 2. + * **euler_bend** (boolean): If `True`, uses Euler bends to route waveguides. Defaults to `False`. Currently only works with slot and strip waveguides. The given `bend_radius` value determines the **smallest** bend radius along the entire Euler curve. * **slot** (float): Size of the waveguide slot region. This is only used if `wg_type`=`'slot'`. Defaults to 0.1. * **period** (float): Period of the SWG. This is only used if `wg_type`=`'swg'`. Defaults to 0.1. * **duty_cycle** (float): Duty cycle of the SWG. This is only used if `wg_type`=`'swg'`. Defaults to 0.5. @@ -30,7 +32,8 @@ def __init__(self, wg_type='strip', bend_radius=50.0, waveguide_stack = None, wg_width=2.0, clad_width=10.0, grid=0.001, resist='+', fab='ETCH', slot=0.1, period=0.1, duty_cycle=0.5, - wg_layer=1, wg_datatype=0, clad_layer=2, clad_datatype=0): + wg_layer=1, wg_datatype=0, clad_layer=2, clad_datatype=0, + euler_bend=False): self.name = tk.getCellName("WaveguideTemplate") #Each WaveguideTemplate is given a unique name if waveguide_stack==None: @@ -74,6 +77,7 @@ def __init__(self, wg_type='strip', bend_radius=50.0, self.resist = '+' if resist=='-' else '-' self.grid = grid + self.euler = euler_bend if self.wg_type =='swg': self.straight_period_cell = gdspy.Cell("swg_seg_"+str(self.wg_width)+"_"+str(self.period)+"_"+str(self.duty_cycle)+"_"+str(self.wg_layer)+"_"+str(self.clad_layer)) @@ -318,9 +322,42 @@ def __build_cell(self): first_path.arc(br, curr_angle+angle_change, curr_angle, **self.wg_spec) self.add(first_path) remaining_period = remaining_period - br*abs(angle_change) + + # add cladding + for i in range(len(self.wgt.waveguide_stack)-1): + cur_width = self.wgt.waveguide_stack[i+1][0] + cur_spec = {'layer': self.wgt.waveguide_stack[i+1][1][0], 'datatype': self.wgt.waveguide_stack[i+1][1][1]} + if len(self.trace)==2: + path2 = gdspy.Path(cur_width, self.trace[0]) + path2.segment(tk.dist(self.trace[0], self.trace[1]), direction=tk.get_exact_angle(self.trace[0], self.trace[1]), **cur_spec) + else: + path2 = gdspy.Path(cur_width, self.trace[0]) + prev_dl = 0.0 + for i in range(len(self.trace)-2): + start_angle = tk.get_exact_angle(self.trace[i], self.trace[i+1]) + next_angle = tk.get_exact_angle(self.trace[i+1], self.trace[i+2]) + + #dl is the amount of distance that is taken *off* the waveguide from the curved section + dl = abs(br*np.tan((next_angle-start_angle)/2.0)) + if (dl+prev_dl) > tk.dist(self.trace[i], self.trace[i+1])+1E-6: + raise ValueError("Warning! The waypoints "+str(self.trace[i])+" and "+str(self.trace[i+1])+" are too close to accommodate " + " the necessary bend-radius of "+str(br)+", the points were closer than "+str(dl+prev_dl)) + + path2.segment(tk.dist(self.trace[i], self.trace[i+1])-dl-prev_dl, + direction=start_angle, **cur_spec) + + turnby = tk.normalize_angle(next_angle - start_angle) + + path2.turn(br, turnby, number_of_points=self.wgt.get_num_points_wg(turnby), **cur_spec) + prev_dl = dl + + path2.segment(tk.dist(self.trace[-2], self.trace[-1])-prev_dl, + direction=next_angle, **cur_spec) + self.add(path2) else: - # Strip and slot waveguide generation below + """ Strip and slot waveguide generation below + """ if len(self.trace)==2: if self.wgt.wg_type=='strip': path = gdspy.Path(self.wgt.wg_width, self.trace[0]) @@ -328,68 +365,84 @@ def __build_cell(self): elif self.wgt.wg_type=='slot': path = gdspy.Path(self.wgt.rail, self.trace[0], number_of_paths=2, distance=self.wgt.rail_dist) path.segment(tk.dist(self.trace[0], self.trace[1]), direction=tk.get_exact_angle(self.trace[0], self.trace[1]), **self.wg_spec) + + clad_path_list = [] + for c in range(len(self.wgt.waveguide_stack)-1): + cur_width = self.wgt.waveguide_stack[c+1][0] + cur_spec = {'layer': self.wgt.waveguide_stack[c+1][1][0], 'datatype': self.wgt.waveguide_stack[c+1][1][1]} + cp = gdspy.Path(cur_width, self.trace[0]) + cp.segment(tk.dist(self.trace[0], self.trace[1]), direction=tk.get_exact_angle(self.trace[0], self.trace[1]), **cur_spec) + clad_path_list.append(cp) + else: if self.wgt.wg_type=='strip': path = gdspy.Path(self.wgt.wg_width, self.trace[0]) elif self.wgt.wg_type=='slot': path = gdspy.Path(self.wgt.rail, self.trace[0], number_of_paths=2, distance=self.wgt.rail_dist) + + clad_path_list = [] + for c in range(len(self.wgt.waveguide_stack)-1): + clad_path_list.append(gdspy.Path(self.wgt.waveguide_stack[c+1][0], self.trace[0])) prev_dl = 0.0 for i in range(len(self.trace)-2): start_angle = tk.get_exact_angle(self.trace[i], self.trace[i+1]) next_angle = tk.get_exact_angle(self.trace[i+1], self.trace[i+2]) + # The following makes sure the turn-by angle is *always* between -pi and +pi + turnby = tk.normalize_angle(next_angle - start_angle) #dl is the amount of distance that is taken *off* the waveguide from the curved section - dl = abs(br*np.tan((next_angle-start_angle)/2.0)) + if self.wgt.euler == False: + dl = abs(br*np.tan((next_angle-start_angle)/2.0)) + else: + # Generate next Euler bend ahead of time + ebend = EBend(self.wgt, turnby, direction=start_angle, vertex=self.trace[i+1]) + dl = ebend.dist_to_vertex + self.add(ebend) + if (dl+prev_dl) > tk.dist(self.trace[i], self.trace[i+1])+1E-6: raise ValueError("Warning! The waypoints "+str(self.trace[i])+" and "+str(self.trace[i+1])+" are too close to accommodate " " the necessary bend-radius of "+str(br)+", the points were closer than "+str(dl+prev_dl)) path.segment(tk.dist(self.trace[i], self.trace[i+1])-dl-prev_dl, direction=start_angle, **self.wg_spec) - - # The following makes sure the turn-by angle is *always* between -pi and +pi - turnby = tk.normalize_angle(next_angle - start_angle) - - path.turn(br, turnby, number_of_points=self.wgt.get_num_points_wg(turnby), **self.wg_spec) + + for c in range(len(self.wgt.waveguide_stack)-1): + cur_spec = {'layer': self.wgt.waveguide_stack[c+1][1][0], 'datatype': self.wgt.waveguide_stack[c+1][1][1]} + clad_path_list[c].segment(tk.dist(self.trace[i], self.trace[i+1])-dl-prev_dl, + direction=start_angle, **cur_spec) + + if self.wgt.euler==False: + path.turn(br, turnby, number_of_points=self.wgt.get_num_points_wg(turnby), **self.wg_spec) + for c in range(len(self.wgt.waveguide_stack)-1): + cur_spec = {'layer': self.wgt.waveguide_stack[c+1][1][0], 'datatype': self.wgt.waveguide_stack[c+1][1][1]} + clad_path_list[c].turn(br, turnby, number_of_points=self.wgt.get_num_points_wg(turnby), **cur_spec) + else: + self.add(path) + for cpath in clad_path_list: + self.add(cpath) + + if self.wgt.wg_type=='strip': + path = gdspy.Path(self.wgt.wg_width, ebend.portlist["output"]["port"]) + elif self.wgt.wg_type=='slot': + path = gdspy.Path(self.wgt.rail, ebend.portlist["output"]["port"], number_of_paths=2, distance=self.wgt.rail_dist) + + for c in range(len(self.wgt.waveguide_stack)-1): + clad_path_list[c] = gdspy.Path(self.wgt.waveguide_stack[c+1][0], ebend.portlist["output"]["port"]) + prev_dl = dl + # Add on the final segment path.segment(tk.dist(self.trace[-2], self.trace[-1])-prev_dl, direction=next_angle, **self.wg_spec) + for c in range(len(self.wgt.waveguide_stack)-1): + cur_spec = {'layer': self.wgt.waveguide_stack[c+1][1][0], 'datatype': self.wgt.waveguide_stack[c+1][1][1]} + clad_path_list[c].segment(tk.dist(self.trace[-2], self.trace[-1])-prev_dl, + direction=next_angle, **cur_spec) self.add(path) - - # add cladding - for i in range(len(self.wgt.waveguide_stack)-1): - cur_width = self.wgt.waveguide_stack[i+1][0] - cur_spec = {'layer': self.wgt.waveguide_stack[i+1][1][0], 'datatype': self.wgt.waveguide_stack[i+1][1][1]} - if len(self.trace)==2: - path2 = gdspy.Path(cur_width, self.trace[0]) - path2.segment(tk.dist(self.trace[0], self.trace[1]), direction=tk.get_exact_angle(self.trace[0], self.trace[1]), **cur_spec) - else: - path2 = gdspy.Path(cur_width, self.trace[0]) - prev_dl = 0.0 - for i in range(len(self.trace)-2): - start_angle = tk.get_exact_angle(self.trace[i], self.trace[i+1]) - next_angle = tk.get_exact_angle(self.trace[i+1], self.trace[i+2]) - - #dl is the amount of distance that is taken *off* the waveguide from the curved section - dl = abs(br*np.tan((next_angle-start_angle)/2.0)) - if (dl+prev_dl) > tk.dist(self.trace[i], self.trace[i+1])+1E-6: - raise ValueError("Warning! The waypoints "+str(self.trace[i])+" and "+str(self.trace[i+1])+" are too close to accommodate " - " the necessary bend-radius of "+str(br)+", the points were closer than "+str(dl+prev_dl)) - - path2.segment(tk.dist(self.trace[i], self.trace[i+1])-dl-prev_dl, - direction=start_angle, **cur_spec) - - turnby = tk.normalize_angle(next_angle - start_angle) - - path2.turn(br, turnby, number_of_points=self.wgt.get_num_points_wg(turnby), **cur_spec) - prev_dl = dl - - path2.segment(tk.dist(self.trace[-2], self.trace[-1])-prev_dl, - direction=next_angle, **cur_spec) - self.add(path2) + for cpath in clad_path_list: + self.add(cpath) def __build_ports(self): @@ -403,14 +456,14 @@ def __build_ports(self): if __name__ == "__main__": gdspy.current_library = gdspy.GdsLibrary() top = gdspy.Cell("top") - wgt1= WaveguideTemplate(wg_type='strip', wg_width=1.0, bend_radius=25, resist='+', fab="ETCH") +# wgt1= WaveguideTemplate(wg_type='strip', wg_width=1.0, bend_radius=25, resist='+', euler_bend=True) wgt2= WaveguideTemplate(wg_type='slot', wg_width=1.0, bend_radius=25, slot=0.3, resist='+', fab="ETCH") wgt3= WaveguideTemplate(wg_type='swg', wg_width=1.0, bend_radius=25, duty_cycle=0.50, period=1.0, resist='+', fab="ETCH") wg_stack = [[0.5, (1,0)], [2.0, (2,0)], [10, (4,0)]] - wgt1= WaveguideTemplate(wg_type='strip', bend_radius=25, waveguide_stack=wg_stack, resist='+', fab="ETCH") + wgt1= WaveguideTemplate(wg_type='slot', wg_width=1.0, bend_radius=25, slot=0.3, waveguide_stack=wg_stack, resist='+', euler_bend=True) space = 10.0 - wg1=Waveguide([(0, 0), (140.0-space, 0), (160.0-space, 50.0), (300.0, 50.0)], wgt1) + wg1=Waveguide([(0, 0), (140.0-space, 0), (160.0-space, 100.0), (300.0, 100.0), (400, 150.0), (200, -300), (-500, 100), (-500, -200)], wgt1) tk.add(top, wg1) wg2=Waveguide([(0, -space), (140.0, -space), (160.0, 50.0-space), (300.0, 50.0-space)], wgt2) tk.add(top, wg2)