def offset( elements, distance=0.1, join_first=True, precision=1e-4, num_divisions=[1, 1], join="miter", tolerance=2, max_points=4000, layer=0, ): """ returns an element containing all polygons with an offset from phidl geometry """ if type(elements) is not list: elements = [elements] polygons_to_offset = [] for e in elements: if isinstance(e, (Device, DeviceReference)): polygons_to_offset += e.get_polygons(by_spec=False) elif isinstance(e, (Polygon, gdspy.Polygon)): polygons_to_offset.append(e) if len(polygons_to_offset) == 0: return pp.Component("offset") polygons_to_offset = _merge_floating_point_errors( polygons_to_offset, tol=precision / 1000 ) gds_layer, gds_datatype = _parse_layer(layer) if all(np.array(num_divisions) == np.array([1, 1])): p = gdspy.offset( polygons_to_offset, distance=distance, join=join, tolerance=tolerance, precision=precision, join_first=join_first, max_points=max_points, layer=gds_layer, datatype=gds_datatype, ) else: p = _offset_polygons_parallel( polygons_to_offset, distance=distance, num_divisions=num_divisions, join_first=join_first, precision=precision, join=join, tolerance=tolerance, ) D = pp.Component("offset") polygons = D.add_polygon(p, layer=layer) [ polygon.fracture(max_points=max_points, precision=precision) for polygon in polygons ] return D
def text( text: str = "abcd", size: float = 10.0, position: Tuple[int, int] = (0, 0), justify: str = "left", layer: Tuple[int, int] = LAYER.TEXT, ) -> Component: """ adds text .. plot:: :include-source: import pp c = pp.c.text(text="abcd", size=10, position=(0, 0), justify="left", layer=1) pp.plotgds(c) """ scaling = size / 1000 xoffset = position[0] yoffset = position[1] t = pp.Component(name=clean_name(text) + "_{}_{}".format(int(position[0]), int(position[1]))) for i, line in enumerate(text.split("\n")): label = pp.Component(name=t.name + "{}".format(i)) for c in line: ascii_val = ord(c) if c == " ": xoffset += 500 * scaling elif 33 <= ascii_val <= 126: for poly in _glyph[ascii_val]: xpts = np.array(poly)[:, 0] * scaling ypts = np.array(poly)[:, 1] * scaling label.add_polygon([xpts + xoffset, ypts + yoffset], layer=layer) xoffset += (_width[ascii_val] + _indent[ascii_val]) * scaling else: ValueError( "[PHIDL] text(): No glyph for character with ascii value %s" % ascii_val) t.add_ref(label) yoffset -= 1500 * scaling xoffset = position[0] justify = justify.lower() for label in t.references: if justify == "left": pass if justify == "right": label.xmax = position[0] if justify == "center": label.move(origin=label.center, destination=position, axis="x") return t
def manhattan_text(text="abcd", size=10, position=(0, 0), justify="left", layer=LAYER.M1): """ .. plot:: :include-source: import pp c = pp.c.text(text="abcd", size=10, position=(0, 0), justify="left", layer=1) pp.plotgds(c) """ pixel_size = size xoffset = position[0] yoffset = position[1] t = pp.Component(name=clean_name(text) + "_{}_{}".format(int(position[0]), int(position[1]))) for i, line in enumerate(text.split("\n")): l = pp.Component(name=t.name + "{}".format(i)) for c in line: try: if c not in CHARAC_MAP: c = c.upper() pixels = CHARAC_MAP[c] except: print( "character {} could not be written (probably not part of dictionnary)" .format(c)) continue _c = l.add_ref( pixel_array(pixels=pixels, pixel_size=pixel_size, layer=layer)) _c.move((xoffset, yoffset)) l.absorb(_c) xoffset += pixel_size * 6 t.add_ref(l) yoffset -= pixel_size * 6 xoffset = position[0] justify = justify.lower() for l in t.references: if justify == "left": pass if justify == "right": l.xmax = position[0] if justify == "center": l.move(origin=l.center, destination=position, axis="x") return t
def circle( radius: float = 10.0, angle_resolution: float = 2.5, layer: Tuple[int, int] = pp.LAYER.WG, ) -> Component: """ Generate a circle geometry. Args: radius: float, Radius of the circle. angle_resolution: float, Resolution of the curve of the ring (# of degrees per point). layer: (int, array-like[2], or set) Specific layer(s) to put polygon geometry on. .. plot:: :include-source: import pp c = pp.c.circle(radius = 10, angle_resolution = 2.5, layer = 0) pp.plotgds(c) """ c = pp.Component() t = np.linspace(0, 360, int(360 / angle_resolution) + 1) * pi / 180 xpts = (radius * cos(t)).tolist() ypts = (radius * sin(t)).tolist() c.add_polygon(points=(xpts, ypts), layer=layer) return c
def linespace( x0: float, y0: float, width: float, height: float, pitch: float, ymax: float, layer: Tuple[int, int], ) -> Component: """Creates a line space pattern in y-direction. Args: x0: x coordinate of the lower left line y0: y coordinate of the lower left line width: width of each line height: height of each line pitch: pitch of each line pitch > height """ assert abs(pitch) < abs(height), "pitch must be greater then height" LS = pp.Component() if pitch > 0: while y0 + height <= ymax: Li = line(x0=x0, y0=y0, width=width, height=height, layer=layer) LS.add_ref(Li) y0 += pitch elif pitch < 0: while y0 + height >= -ymax: Li = line(x0=x0, y0=y0, width=width, heigh=height, layer=layer) LS.add_ref(Li) y0 += pitch return LS
def align_wafer( width=10.0, spacing=10.0, cross_length=80.0, layer=pp.LAYER.WG, with_tile_excl=True, square_corner="bottom_left", ): """ returns cross inside a frame to align wafer .. plot:: :include-source: import pp c = pp.c.align_wafer() pp.plotgds(c) """ c = pp.Component() cross = pp.c.cross(length=cross_length, width=width, layer=layer) c.add_ref(cross) b = cross_length / 2 + spacing + width / 2 w = width rh = rectangle(size=(2 * b + w, w), layer=layer, centered=True) rtop = c.add_ref(rh) rbot = c.add_ref(rh) rtop.movey(+b) rbot.movey(-b) rv = rectangle(w=w, h=2 * b, layer=layer, centered=True) rl = c.add_ref(rv) rr = c.add_ref(rv) rl.movex(-b) rr.movex(+b) wsq = (cross_length + 2 * spacing) / 4 square_mark = c << rectangle(size=(wsq, wsq), layer=layer, centered=True) a = width / 2 + wsq / 2 + spacing corner_to_position = { "bottom_left": (-a, -a), "bottom_right": (a, -a), "top_right": (a, a), "top_left": (-a, a), } square_mark.move(corner_to_position[square_corner]) if with_tile_excl: rc_tile_excl = rectangle( size=(2 * (b + spacing), 2 * (b + spacing)), layer=pp.LAYER.NO_TILE_SI, centered=True, ) c.add_ref(rc_tile_excl) return c
def reticle_mockup(): from pp.layers import LAYER dx = 9000.0 dy = 8000.0 a0 = 50.0 component = pp.Component() for x in [0, dx, 2 * dx, 3 * dx]: a = x + a0 b = x - a0 component.add_polygon( [(a, 0), (b, 0), (b, 3 * dy), (a, 3 * dy)], LAYER.FLOORPLAN ) for y in [0, dy, 2 * dy, 3 * dy]: a = y + a0 b = y - a0 component.add_polygon( [(0, a), (0, b), (3 * dx, b), (3 * dx, a)], LAYER.FLOORPLAN ) c = mini_block_mockup() for i, j in [(0, 0), (2, 2), (0, 2), (2, 0), (1, 1)]: component.add(c.ref(position=(i * dx, j * dy))) return component
def test_connect_bundle_waypointsB(): import pp from pp.component import Port ys1 = np.array([0, 5, 10, 15, 30, 40, 50, 60]) + 0.0 ys2 = np.array([0, 10, 20, 30, 70, 90, 110, 120]) + 500.0 N = ys1.size ports1 = [Port("A_{}".format(i), (0, ys1[i]), 0.5, 0) for i in range(N)] ports2 = [ Port("B_{}".format(i), (500, ys2[i]), 0.5, 180) for i in range(N) ] p0 = ports1[0].position + (0, 22.5) top_cell = pp.Component() way_points = [ p0, p0 + (200, 0), p0 + (200, -200), p0 + (400, -200), (p0[0] + 400, ports2[0].y), ports2[0].position, ] elements = connect_bundle_waypoints(ports1, ports2, way_points) top_cell.add(elements) return top_cell
def demo(): """plot curvature of bends """ from matplotlib import pyplot as plt c = crossing45(port_spacing=20.0, dx=15) c2 = compensation_path(crossing45=c) print(c.info["min_bend_radius"]) print(c2.info["min_bend_radius"]) component = pp.Component(name="top_lvl") component.add(c.ref(port_id="W0")) component.add(c2.ref(port_id="W0", position=(0, 10))) bend_info1 = c.info["components"]["bezier_bend"].info bend_info2 = c2.info["components"]["sbend"].info DL = bend_info1["length"] L2 = bend_info1["length"] plt.plot(bend_info1["t"][1:-1] * DL, abs(bend_info1["curvature"])) plt.plot(bend_info2["t"][1:-1] * L2, abs(bend_info2["curvature"])) plt.xlabel("bend length (um)") plt.ylabel("curvature (um^-1)") pp.show(component) plt.show()
def add_pins( component: Component, function: Callable = _add_pins_labels_and_outline, recursive: bool = False, ) -> Component: """Add pins to a Component and returns a container Args: component: function: function to add pins recursive: goes down the hierarchy Returns: New component """ component_new = pp.Component(f"{component.name}_pins") reference = component_new << component function(component=component_new, reference=reference) if recursive: for reference in component.references: function(component=component_new, reference=reference) return component_new
def ellipse( radii: Tuple[float, float] = (10.0, 5.0), angle_resolution: float = 2.5, layer: Tuple[int, int] = pp.LAYER.WG, ) -> Component: """Generate an ellipse geometry. Args: radii: (tuple) Semimajor and semiminor axis lengths of the ellipse. angle_resolution: (float) Resolution of the curve of the ring (# of degrees per point). layer: (int, array-like[2], or set) Specific layer(s) to put polygon geometry on. The orientation of the ellipse is determined by the order of the radii variables; if the first element is larger, the ellipse will be horizontal and if the second element is larger, the ellipse will be vertical. .. plot:: :include-source: import pp c = pp.c.ellipse(radii=(10, 5), angle_resolution=2.5, layer=0) pp.plotgds(c) """ D = pp.Component() a = radii[0] b = radii[1] t = np.linspace(0, 360, int(360 / angle_resolution) + 1) * pi / 180 r = a * b / (sqrt((b * cos(t))**2 + (a * sin(t))**2)) xpts = r * cos(t) ypts = r * sin(t) D.add_polygon(points=(xpts, ypts), layer=layer) return D
def test_connect_u_direct(): w = h = 10 c = pp.Component() pad_south = pp.c.pad_array(port_list=["S"], spacing=(15, 0), width=w, height=h) pt = c << pad_south pb = c << pad_south pb.rotate(90) pt.rotate(90) pb.move((0, -100)) pbports = pb.get_ports_list() ptports = pt.get_ports_list() pbports.reverse() r = pp.routing.connect_bundle(pbports, ptports) # r = pp.routing.link_ports(pbports, ptports) # does not work c.add(r) # print(r[0].parent.length) # print(r[1].parent.length) # print(r[2].parent.length) # print(r[3].parent.length) # print(r[4].parent.length) # print(r[5].parent.length) assert np.isclose(r[0].parent.length, 36.435926535897934) assert np.isclose(r[1].parent.length, 76.43592653589793) assert np.isclose(r[2].parent.length, 116.43592653589793) assert np.isclose(r[3].parent.length, 156.43592653589792) assert np.isclose(r[4].parent.length, 196.43592653589795) assert np.isclose(r[5].parent.length, 236.43592653589795) return c
def test_connect_bundle_optical3(): """ connect 4 waveguides into a 4x1 component """ c = pp.Component() w = c << pp.c.waveguide_array(n_waveguides=4, spacing=200) d = c << pp.c.nxn(west=4, east=1) d.y = w.y d.xmin = w.xmax + 200 ports1 = w.get_ports_list(prefix="E") ports2 = d.get_ports_list(prefix="W") r = pp.routing.link_optical_ports(ports1, ports2, sort_ports=True) # print(r[0].parent.length) # print(r[1].parent.length) # print(r[2].parent.length) # print(r[3].parent.length) assert np.isclose(r[0].parent.length, 489.4159265358979) assert np.isclose(r[3].parent.length, 489.4159265358979) assert np.isclose(r[1].parent.length, 290.74892653589797) assert np.isclose(r[2].parent.length, 290.74892653589797) c.add(r) return c
def test_route_south(): c = pp.Component() cr = c << pp.c.mmi2x2() routes, ports = pp.routing.route_south(cr) lengths = [ 15.708, 1.0, 0.5, 15.708, 5.0, 1.6499999999999968, 15.708, 1.0, 0.5, 15.708, 5.0, 1.6499999999999968, ] for route, length in zip(routes, lengths): c.add(route) route_length = route.parent.get_settings()["info"]["length"] print(route_length) assert np.isclose(route_length, length) return c
def test_path_length_matching_nb_loops(): c = pp.Component("path_length_match_sample") dy = 2000.0 xs1 = [-500, -300, -100, -90, -80, -55, -35, 200, 210, 240, 500, 650] pitch = 100.0 N = len(xs1) xs2 = [-20 + i * pitch for i in range(N)] a1 = 90 a2 = a1 + 180 ports1 = [pp.Port(f"top_{i}", (xs1[i], 0), 0.5, a1) for i in range(N)] ports2 = [pp.Port(f"bottom_{i}", (xs2[i], dy), 0.5, a2) for i in range(N)] routes = pp.routing.connect_bundle_path_length_match(ports1, ports2, nb_loops=2) for route in routes: # print(route.parent.length) assert np.isclose(route.parent.length, 2681.07963267949) c.add(routes) c.routes = routes return c
def crossing(arm: Callable = crossing_arm) -> Component: """waveguide crossing .. plot:: :include-source: import pp c = pp.c.crossing() pp.plotgds(c) """ cx = pp.Component() arm = pp.call_if_func(arm) arm_h = arm.ref() arm_v = arm.ref(rotation=90) port_id = 0 for c in [arm_h, arm_v]: cx.add(c) cx.absorb(c) for p in c.ports.values(): cx.add_port(name="{}".format(port_id), port=p) port_id += 1 cx = pp.port.rename_ports_by_orientation(cx) return cx
def cross(x0: float, y0: float, width: float, lw: float, layer: Tuple[int, int]) -> Component: """cross Args: x0,y0 : center width: width of the bounding box lw: linewidth """ cross = pp.Component() cross.add_polygon( [ (x0 - width / 2, y0 - lw / 2), (x0 - width / 2, y0 + lw / 2), (x0 + width / 2, y0 + lw / 2), (x0 + width / 2, y0 - lw / 2), ], layer=layer, ) cross.add_polygon( [ (x0 - lw / 2, y0 - width / 2), (x0 - lw / 2, y0 + width / 2), (x0 + lw / 2, y0 + width / 2), (x0 + lw / 2, y0 - width / 2), ], layer=layer, ) return cross
def add_padding( component: Component, padding: Union[float, int] = 50, x: None = None, y: None = None, layers: Union[List[ListConfig], List[Layer]] = [pp.LAYER.PADDING], suffix: str = "p", ) -> Component: """adds padding layers to a NEW component that has the same: - ports - settings - test_protocols and data_analysis_protocols as the old component """ x = x if x is not None else padding y = y if y is not None else padding c = pp.Component(name=f"{component.name}_{suffix}") c << component c.ports = component.ports points = [ [c.xmin - x, c.ymin - y], [c.xmax + x, c.ymin - y], [c.xmax + x, c.ymax + y], [c.xmin - x, c.ymax + y], ] for layer in layers: c.add_polygon(points, layer=layer) return c
def test_connect_bundle_optical3(): """ connect 4 waveguides into a 4x1 component """ c = pp.Component() w = c << pp.c.waveguide_array(n_waveguides=4, spacing=200) d = c << pp.c.nxn(west=4, east=1) d.y = w.y d.xmin = w.xmax + 200 ports1 = w.get_ports_list(prefix="E") ports2 = d.get_ports_list(prefix="W") routes = pp.routing.link_optical_ports(ports1, ports2, sort_ports=True) lengths = [ 489.416, 290.749, 290.749, 489.416, ] for route, length in zip(routes, lengths): # print(route["settings"]["length"]) c.add(route["references"]) assert np.isclose(route["settings"]["length"], length) return c
def _TRCH_DASH_DUO(length=20.0, gap=2.0, width=0.5, separation=4.0, n=3, x_offset=0.0, label=""): _trench = _TRCH_DASH_ISO(length=length, width=width, n=n, separation=separation) dx = x_offset dy = gap + width c = pp.Component() t1 = c.add_ref(_trench) t2 = c.add_ref(_trench) t2.move((dy, dx)) c.absorb(t1) c.absorb(t2) if label: marker_label = manhattan_text(text=label, size=0.4, layer=LAYER.WG) _marker_label = c.add_ref(marker_label) _marker_label.movey((n - 1) * (length + separation + 4.0) + length / 2) c.absorb(_marker_label) return c
def test_connect_bundle_waypointsA(): import pp from pp.component import Port xs1 = np.arange(10) * 5 - 500.0 N = xs1.size ys2 = np.array([0, 5, 10, 20, 25, 30, 40, 55, 60, 75]) + 500.0 ports1 = [Port("A_{}".format(i), (xs1[i], 0), 0.5, 90) for i in range(N)] ports2 = [Port("B_{}".format(i), (0, ys2[i]), 0.5, 180) for i in range(N)] top_cell = pp.Component() p0 = ports1[0].position + (22, 0) way_points = [ p0, p0 + (0, 100), p0 + (200, 100), p0 + (200, -200), p0 + (0, -200), p0 + (0, -350), p0 + (400, -350), (p0[0] + 400, ports2[-1].y), ports2[-1].position, ] elements = connect_bundle_waypoints(ports1, ports2, way_points) top_cell.add(elements) return top_cell
def snapping_error(gap=1e-3): c = pp.Component() r1 = c << pp.c.rectangle(size=(1, 1), layer=layer) r2 = c << pp.c.rectangle(size=(1, 1), layer=layer) r1.xmax = 0 r2.xmin = gap return c
def test_connect_bundle_waypointsC(): import pp from pp.component import Port ys1 = np.array([0, 5, 10, 15, 20, 60, 70, 80, 120, 125]) ys2 = np.array([0, 5, 10, 20, 25, 30, 40, 55, 60, 65]) - 500.0 N = ys1.size ports1 = [Port("A_{}".format(i), (0, ys1[i]), 0.5, 0) for i in range(N)] ports2 = [ Port("B_{}".format(i), (600, ys2[i]), 0.5, 180) for i in range(N) ] top_cell = pp.Component() way_points = [ ports1[0].position, ports1[0].position + (200, 0), ports1[0].position + (200, -200), ports1[0].position + (400, -200), (ports1[0].x + 400, ports2[0].y), ports2[0].position, ] elements = connect_bundle_waypoints(ports1, ports2, way_points) top_cell.add(elements) return top_cell
def _via_iterable(via_spacing, wire_width, wiring1_layer, wiring2_layer, via_layer, via_width): VI = pp.Component() wire1 = VI.add_ref( pc.compass(size=(via_spacing, wire_width), layer=wiring1_layer)) wire2 = VI.add_ref( pc.compass(size=(via_spacing, wire_width), layer=wiring2_layer)) via1 = VI.add_ref(pc.compass(size=(via_width, via_width), layer=via_layer)) via2 = VI.add_ref(pc.compass(size=(via_width, via_width), layer=via_layer)) wire1.connect(port="E", destination=wire2.ports["W"], overlap=wire_width) via1.connect(port="W", destination=wire1.ports["E"], overlap=(wire_width + via_width) / 2) via2.connect(port="W", destination=wire2.ports["E"], overlap=(wire_width + via_width) / 2) VI.add_port(name="W", port=wire1.ports["W"]) VI.add_port(name="E", port=wire2.ports["E"]) VI.add_port( name="S", midpoint=[(1 * wire_width) + wire_width / 2, -wire_width / 2], width=wire_width, orientation=-90, ) VI.add_port( name="N", midpoint=[(1 * wire_width) + wire_width / 2, wire_width / 2], width=wire_width, orientation=90, ) return VI
def gap_min(gap=0.1): c = pp.Component() r1 = c << pp.c.rectangle(size=(1, 1), layer=layer) r2 = c << pp.c.rectangle(size=(1, 1), layer=layer) r1.xmax = 0 r2.xmin = gap return c
def githash(text=[], size=0.4, hash_length=6, layer=LAYER.WG): """ returns the photonics_pdk git hash allows a list of text, that will print on separate lines :: text = [ "sw_{}".format(Repo(CONFIG["repo"]).head.object.hexsha[:length]), "ap_{}".format(Repo(ap.CONFIG["repo"]).head.object.hexsha[:length]), "mm_{}".format(Repo(mm.CONFIG["repo"]).head.object.hexsha[:length]), ] c = githash(text=text) pp.write_gds(c) pp.show(c) """ try: git_hash = "pp_{}".format(pp.CONFIG["repo"][:hash_length]) except Exception: git_hash = "pp_{}".format(pp.__version__) c = pp.Component() t = manhattan_text(text=git_hash, size=size, layer=layer) tref = c.add_ref(t) c.absorb(tref) for i, texti in enumerate(text): t = manhattan_text(text=texti, size=size, layer=layer) tref = c.add_ref(t) tref.movey(-6 * size * (i + 1)) c.absorb(tref) return c
def align_cryo_top_left(x=60, y=60, s=0.2, layer=1): c = pp.Component() points = [[0, 0], [s, 0], [x - s, y - s], [x - s, y], [0, y]] c.add_polygon(points, layer=layer) cc = add_frame(component=c) cc.name = "align_cryo_top_left" return cc
def cdsem_straight_density(wg_width=0.372, trench_width=0.304, x=500, y=50.0, margin=2.0): """ horizontal grating etch lines TE: 676nm pitch, 304nm gap, 372nm line TM: 1110nm pitch, 506nm gap, 604nm line Args: w: wg_width s: trench_width """ c = pp.Component() period = wg_width + trench_width n_o_lines = int((y - 2 * margin) / period) length = x - 2 * margin slab = pp.c.rectangle_centered(x=x, y=y, layer=LAYER.WG) slab_ref = c.add_ref(slab) c.absorb(slab_ref) tooth = pp.c.rectangle_centered(x=length, y=trench_width, layer=LAYER.SLAB150) for i in range(n_o_lines): _tooth = c.add_ref(tooth) _tooth.movey((-n_o_lines / 2 + 0.5 + i) * period) c.absorb(_tooth) c.move(c.size_info.cc, (0, 0)) return c
def add_frame(component, width=10, spacing=10, layer=pp.LAYER.WG): """ returns component with a frame around it """ c = pp.Component() cref = c.add_ref(component) cref.move(-c.size_info.center) b = component.size_info.height / 2 + spacing + width / 2 w = width rh = rectangle(size=(2 * b + w, w), layer=layer, centered=True) rtop = c.add_ref(rh) rbot = c.add_ref(rh) rtop.movey(+b) rbot.movey(-b) rv = rectangle(size=(w, 2 * b), layer=layer, centered=True) rl = c.add_ref(rv) rr = c.add_ref(rv) rl.movex(-b) rr.movex(+b) c.absorb(cref) rc = rectangle( size=(2 * (b + spacing), 2 * (b + spacing)), layer=pp.LAYER.NO_TILE_SI, centered=True, ) c.add_ref(rc) return c
def test_connect_bundle_u_indirect(dy=-200, angle=180): xs1 = [-100, -90, -80, -55, -35] + [200, 210, 240] axis = "X" if angle in [0, 180] else "Y" pitch = 10.0 N = len(xs1) xs2 = [50 + i * pitch for i in range(N)] a1 = angle a2 = a1 + 180 if axis == "X": ports1 = [Port("top_{}".format(i), (0, xs1[i]), 0.5, a1) for i in range(N)] ports2 = [Port("bottom_{}".format(i), (dy, xs2[i]), 0.5, a2) for i in range(N)] else: ports1 = [Port("top_{}".format(i), (xs1[i], 0), 0.5, a1) for i in range(N)] ports2 = [Port("bottom_{}".format(i), (xs2[i], dy), 0.5, a2) for i in range(N)] top_cell = pp.Component() elements = connect_bundle(ports1, ports2) for e in elements: top_cell.add(e) return top_cell