def add_keepout(component, target_layers, keepout_layers, margin=2.0) -> Component: """Adds keepout after Looking up all polygons in a cell. You can also use add_padding Args: component target_layers: list of layers to read keepout_layers: list of layers to add keepout margin: offset from tareget to keepout_layers """ c = Component(f"{component.name}_ko") c << component for layer in target_layers: polygons = component.get_polygons(by_spec=layer) if polygons: for ko_layer in keepout_layers: ko_layer = _parse_layer(ko_layer) polygon_keepout = [ polygon_grow(polygon, margin) for polygon in polygons ] c.add_polygon(polygon_keepout, ko_layer) return c
def containerize(component: Component, function: Callable, **kwargs): """Returns a containerize component after applying a function. This is an alternative of using the @container decorator. However I recommend using the decorator when possible Args: component: to containerize function: that applies to component **kwargs: for the function .. code:: import pp def add_label(component, text='hi'): return component.add_label(text) c = pp.c.waveguide() cc = pp.containerize(c, function=add_label, text='hi') """ c = Component() component_type = f"{component.name}_{function.__name__}" name = get_component_name(component_type, **kwargs) if len(name) > MAX_NAME_LENGTH: c.name_long = name name = f"{component_type}_{hashlib.md5(name.encode()).hexdigest()[:8]}" c.name = name c << component function(c, **kwargs) return c
def add_electrical_pads_top(component, **kwargs): """connects component electrical ports with pad array at the top Args: component: pad: pad element spacing: pad array (x, y) spacing width: pad width height: pad height layer: pad layer """ c = Component(f"{component.name}_e") ports = component.get_ports_list(port_type="dc") c << component pads = c << pad_array(n=len(ports), port_list=["S"], **kwargs) pads.x = component.x pads.y = component.ymax + 100 ports_pads = list(pads.ports.values()) for p1, p2 in zip(ports_pads, ports): c.add(connect_electrical_shortest_path(p1, p2)) c.ports = component.ports for port in ports: c.ports.pop(port.name) return c
def demo_connect_bundle(): """ combines all the connect_bundle tests """ y = 400.0 x = 500 y0 = 900 dy = 200.0 c = Component("connect_bundle") for j, s in enumerate([-1, 1]): for i, angle in enumerate([0, 90, 180, 270]): c2 = test_connect_bundle_u_indirect(dy=s * dy, angle=angle) c2ref = c2.ref(position=(i * x, j * y)) c.add(c2ref) c2 = test_connect_bundle_udirect(dy=s * dy, angle=angle) c2ref = c2.ref(position=(i * x, j * y + y0)) c.add(c2ref) for i, config in enumerate(["A", "B", "C", "D"]): c2 = test_connect_corner(config=config) c2ref = c2.ref(position=(i * x, 1700)) c.add(c2ref) c2 = test_facing_ports() c2ref = c2.ref(position=(800, 1820)) c.add(c2ref) return c
def add_labels( component: Component, port_type: str = "dc", get_label_function: Callable = get_input_label_electrical, layer_label: Layer = pp.LAYER.LABEL, gc: Optional[Component] = None, ) -> Component: """Add labels a particular type of ports Args: component: to add labels to port_type: type of port ('dc', 'optical', 'electrical') get_label_function: function to get label layer_label: layer_label Returns: original component with labels """ ports = component.get_ports_list(port_type=port_type) for i, port in enumerate(ports): label = get_label_function( port=port, gc=gc, gc_index=i, component_name=component.name, layer_label=layer_label, ) component.add(label) return component
def write_gds( component: Component, gdspath: Optional[PosixPath] = None, gdsdir: PosixPath = tmp, unit: float = 1e-6, precision: float = 1e-9, auto_rename: bool = False, ) -> PosixPath: """Write component to GDS and returs gdspath Args: component: gdsfactory Component. gdspath: GDS file path to write to. unit unit size for objects in library. precision: for the dimensions of the objects in the library (m). remove_previous_markers: clear previous ones to avoid duplicates. auto_rename: If True, fixes any duplicate cell names. Returns: gdspath """ gdsdir = pathlib.Path(gdsdir) gdspath = gdspath or gdsdir / (component.name + ".gds") gdspath = pathlib.Path(gdspath) gdsdir = gdspath.parent gdsdir.mkdir(exist_ok=True, parents=True) component.write_gds( str(gdspath), unit=unit, precision=precision, auto_rename=auto_rename, ) component.path = gdspath return gdspath
def add_trenches( c: Component, sstw: float = 2.0, trench_width: float = 0.5, trench_keep_out: float = 2.0, trenches: List[Dict[str, int]] = [ { "nb_segments": 2, "lane": 1, "x_start_offset": 0 }, { "nb_segments": 2, "lane": -1, "x_start_offset": 0 }, ], layer_trench: Tuple[int, int] = LAYER.DEEPTRENCH, ) -> Component: """ Add trenches to a waveguide-heater-like component """ heater_width = c.settings["heater_width"] heater_spacing = c.settings["heater_spacing"] width = c.settings["width"] length = c.settings["length"] a = heater_spacing + (width + heater_width) / 2 # Add trenches if trench_width and trench_width > 0: tko = trench_keep_out for trench in trenches: lane = trench["lane"] td = tko + a + (trench_width + heater_width) / 2 y = np.sign(lane) * (td + (abs(lane) - 1) * (trench_width + tko)) x_start_offset = trench["x_start_offset"] if "segments" not in trench: nb_segments = trench["nb_segments"] trench_length = (length - (nb_segments - 1) * sstw) / nb_segments segments = [trench_length] * nb_segments else: segments = trench["segments"] x = x_start_offset for i, trench_length in enumerate(segments): trench = hline(length=trench_length, width=trench_width, layer=layer_trench) _trench = trench.ref(port_id="W0", position=c.ports["W0"].position + (x, y)) c.add(_trench) c.absorb(_trench) x += trench_length + sstw return c
def _add_pin_square( component: Component, port: Port, pin_length: float = 0.1, layer: Tuple[int, int] = LAYER.PORT, label_layer: Optional[Tuple[int, int]] = LAYER.PORT, port_margin: float = 0.0, ) -> None: """Add half out pin to a component. Args: component: port: Port pin_length: length of the pin marker for the port layer: for the pin marker label_layer: for the label port_margin: margin to port edge .. code:: _______________ | | | | | | ||| | ||| | | | | __ | |_______________| __ """ p = port a = p.orientation ca = np.cos(a * np.pi / 180) sa = np.sin(a * np.pi / 180) rot_mat = np.array([[ca, -sa], [sa, ca]]) d = p.width / 2 + port_margin dbot = np.array([pin_length / 2, -d]) dtop = np.array([pin_length / 2, d]) dbotin = np.array([-pin_length / 2, -d]) dtopin = np.array([-pin_length / 2, +d]) p0 = p.position + _rotate(dbot, rot_mat) p1 = p.position + _rotate(dtop, rot_mat) ptopin = p.position + _rotate(dtopin, rot_mat) pbotin = p.position + _rotate(dbotin, rot_mat) polygon = [p0, p1, ptopin, pbotin] component.add_polygon(polygon, layer=layer) if label_layer: component.add_label( text=str(p.name), position=p.midpoint, layer=label_layer, )
def rotate(component: Component, angle: int = 90) -> Component: """ returns rotated component """ c = Component(f"{component.name}_r") cr = c.add_ref(component) cr.rotate(angle) c.ports = cr.ports return c
def write_gds( component: Component, gdspath: Optional[PosixPath] = None, unit: float = 1e-6, precision: float = 1e-9, remove_previous_markers: bool = False, auto_rename: bool = False, with_settings_label: bool = conf.tech.with_settings_label, ) -> str: """write component to GDS and returs gdspath Args: component (required) gdspath: by default saves it into CONFIG['gds_directory'] auto_rename: False by default (otherwise it calls it top_cell) unit precission Returns: gdspath """ gdspath = gdspath or CONFIG["gds_directory"] / (component.name + ".gds") gdspath = pathlib.Path(gdspath) gdsdir = gdspath.parent gdspath = str(gdspath) gdsdir.mkdir(parents=True, exist_ok=True) if remove_previous_markers: # If the component HAS ports AND markers and we want to # avoid duplicate port markers, then we clear the previous ones port_layer = (LAYER.PORT, ) label_layer = (LAYER.TEXT, ) component.remove_layers([port_layer]) component.remove_layers([label_layer]) # write component settings into text layer if with_settings_label: settings = component.get_settings() for i, k in enumerate(sorted(list(settings.keys()))): v = clean_value(settings.get(k)) text = f"{k} = {clean_value(v)}" # print(text) component.add_label( text=text, position=component.center - [0, i * 1], layer=CONFIG["layers"]["TEXT"], ) component.write_gds( gdspath, precision=precision, auto_rename=auto_rename, ) component.path = gdspath return gdspath
def corner(width=WIRE_WIDTH, radius=None, layer=LAYER.M3): """ 90 degrees electrical bend Args: width: wire width radius ignore (passed for consistency with other types of bends) layer: layer .. plot:: :include-source: import pp c = pp.c.corner(width=10., layer=pp.LAYER.M3) pp.plotgds(c) """ c = Component() a = width / 2 xpts = [-a, a, a, -a] ypts = [-a, -a, a, a] c.add_polygon([xpts, ypts], layer=layer) c.add_port(name="W0", midpoint=(-a, 0), width=width, orientation=180) c.add_port(name="N0", midpoint=(0, a), width=width, orientation=90) c.info["length"] = width return c
def _add_settings_label( component: Component, reference: ComponentReference, label_layer: Tuple[int, int] = LAYER.LABEL_SETTINGS, ) -> None: """Add settings in label, ignores component.ignore keys.""" settings = reference.get_settings() settings_string = f"settings={json.dumps(settings, indent=2)}" component.add_label( position=reference.center, text=settings_string, layer=label_layer )
def pads_shorted(width=100, n_pads=8, pad_spacing=150, layer=LAYER.M1): c = Component(name="shorted_pads") pad = rectangle_centered(x=width, y=width, layer=layer) for i in range(n_pads): pad_ref = c.add_ref(pad) pad_ref.movex(i * pad_spacing - n_pads / 2 * pad_spacing + pad_spacing / 2) short = rectangle_centered(x=pad_spacing * (n_pads - 1), y=10, layer=layer) c.add_ref(short) return c
def rotate(component, angle=90): """ returns rotated component """ c = Component( settings=component.get_settings(), test_protocol=component.test_protocol, data_analysis_protocol=component.data_analysis_protocol, ) cr = c.add_ref(component) cr.rotate(angle) c.ports = cr.ports c.name = component.name + "_r" return c
def pads_shorted(width=100, n_pads=8, pad_spacing=150, layer=LAYER.M1): c = Component(name="shorted_pads") pad = rectangle(size=(width, width), layer=layer, centered=True) for i in range(n_pads): pad_ref = c.add_ref(pad) pad_ref.movex(i * pad_spacing - n_pads / 2 * pad_spacing + pad_spacing / 2) short = rectangle(size=(pad_spacing * (n_pads - 1), 10), layer=layer, centered=True) c.add_ref(short) 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 = Component("connect_bundle_u_indirect") routes = connect_bundle(ports1, ports2) lengths = [ 341.41592653589794, 341.41592653589794, 341.41592653589794, 326.41592653589794, 316.41592653589794, 291.41592653589794, 291.41592653589794, 311.41592653589794, ] for route, length in zip(routes, lengths): # print(route.parent.length) assert np.isclose(route.parent.length, length) top_cell.add(routes) return top_cell
def bbox(bbox=[(-1, -1), (3, 4)], layer=(1, 0)): """ Creates a bounding box rectangle from coordinates, to allow creation of a rectangle bounding box directly form another shape. Args: bbox: Coordinates of the box [(x1, y1), (x2, y2)]. layer: """ D = Component(name="bbox") (a, b), (c, d) = bbox points = ((a, b), (c, b), (c, d), (a, d)) D.add_polygon(points, layer=layer) return D
def L( width: Union[int, float] = 1, size: Tuple[int, int] = (10, 20), layer: Tuple[int, int] = LAYER.M3, ) -> Component: """Generates an 'L' geometry with ports on both ends. Based on phidl. Args: width: of the line size: length and height of the base layer: .. plot:: :include-source: import pp c = pp.c.L(width1=1, size=(10, 20), layer=pp.LAYER.M3) pp.plotgds(c) """ D = Component() w = width / 2 s1, s2 = size points = [(-w, -w), (s1, -w), (s1, w), (w, w), (w, s2), (-w, s2), (-w, -w)] D.add_polygon(points, layer=layer) D.add_port(name=1, midpoint=(0, s2), width=width, orientation=90) D.add_port(name=2, midpoint=(s1, 0), width=width, orientation=0) return D
def add_grating_couplers( component: Component, grating_coupler=grating_coupler_te, layer_label=pp.LAYER.LABEL, gc_port_name: str = "W0", get_input_labels_function=get_input_labels, ): """Return component with grating couplers and labels.""" cnew = Component(name=component.name + "_c") cnew.add_ref(component) grating_coupler = pp.call_if_func(grating_coupler) io_gratings = [] for port in component.ports.values(): gc_ref = grating_coupler.ref() gc_ref.connect(list(gc_ref.ports.values())[0], port) io_gratings.append(gc_ref) cnew.add(gc_ref) labels = get_input_labels_function( io_gratings, list(component.ports.values()), component_name=component.name, layer_label=layer_label, gc_port_name=gc_port_name, ) cnew.add(labels) return cnew
def _add_pin_square_inside( component: Component, port: Port, pin_length: float = 0.1, layer: Tuple[int, int] = LAYER.PORT, label_layer: Tuple[int, int] = LAYER.TEXT, ) -> None: """Add square pin towards the inside of the port Args: component: port: Port pin_length: length of the pin marker for the port layer: for the pin marker label_layer: for the label port_margin: margin to port edge .. code:: _______________ | | | | | | || | || | | | | __ | |_______________| """ p = port a = p.orientation ca = np.cos(a * np.pi / 180) sa = np.sin(a * np.pi / 180) rot_mat = np.array([[ca, -sa], [sa, ca]]) d = p.width / 2 dbot = np.array([0, -d]) dtop = np.array([0, d]) dbotin = np.array([-pin_length, -d]) dtopin = np.array([-pin_length, +d]) p0 = p.position + _rotate(dbot, rot_mat) p1 = p.position + _rotate(dtop, rot_mat) ptopin = p.position + _rotate(dtopin, rot_mat) pbotin = p.position + _rotate(dbotin, rot_mat) polygon = [p0, p1, ptopin, pbotin] component.add_polygon(polygon, layer=layer)
def test_connect_bundle_udirect(dy=200, angle=270): xs1 = [-100, -90, -80, -55, -35, 24, 0] + [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)] if axis == "X": ports1 = [ Port("top_{}".format(i), (0, xs1[i]), 0.5, angle) for i in range(N) ] ports2 = [ Port("bottom_{}".format(i), (dy, xs2[i]), 0.5, angle) for i in range(N) ] else: ports1 = [ Port("top_{}".format(i), (xs1[i], 0), 0.5, angle) for i in range(N) ] ports2 = [ Port("bottom_{}".format(i), (xs2[i], dy), 0.5, angle) for i in range(N) ] top_cell = Component(name="connect_bundle_udirect") routes = connect_bundle(ports1, ports2) lengths = [ 237.4359265358979, 281.4359265358979, 336.4359265358979, 376.4359265358979, 421.4359265358979, 451.4359265358979, 481.4359265358979, 271.4359265358979, ] for route, length in zip(routes, lengths): # print(route.parent.length) assert np.isclose(route.parent.length, length) top_cell.add(routes) return top_cell
def _add_padding(component, x=50, y=50, layers=(LAYER.PADDING), suffix="p"): """Adds padding layers to component. This is just an example. For the real function see pp.add_padding. """ c = Component(name=f"{component.name}_{suffix}") c << component 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 read_yaml(yaml: Union[str, pathlib.Path, IO[Any]]) -> Component: """ Loads Components settings from yaml file and writes the GDS into build_directory Args: yaml: YAML IO describing DOE Returns: Component """ c = Component() yaml = io.StringIO(yaml) if isinstance(yaml, str) and "\n" in yaml else yaml conf = OmegaConf.load(yaml) for component_name in conf: component_conf = conf[component_name] component_type = component_conf["component"] component_settings = component_conf["settings"] ci = component_factory[component_type](**component_settings) component_properties = component_conf["properties"] for k, v in component_properties.items(): setattr(ci, k, v) ci.name = component_name c << ci return c
def get_sparameters_path( component: Component, dirpath: PosixPath = CONFIG["sp"], layer2material: Dict[Tuple[int, int], str] = layer2material_default, layer2nm: Dict[Tuple[int, int], int] = layer2nm_default, **kwargs, ) -> PosixPath: """Returns Sparameters filepath. Args: component: dirpath layer2material: GDSlayer to material alias (see aliases in pp.sp.write) layer2nm: GDSlayer to thickness (nm) """ dirpath = pathlib.Path(dirpath) dirpath = dirpath / component.function_name if component.function_name else dirpath dirpath.mkdir(exist_ok=True, parents=True) material2nm = { layer2material[layer]: layer2nm[layer] for layer in layer2nm.keys() if layer in component.get_layers() } suffix = dict2name(**material2nm) if kwargs: suffix += "_" + dict2name(**kwargs) return dirpath / f"{component.get_name_long()}_{suffix}.dat"
def _add_instance_label( component: Component, reference: ComponentReference, instance_name: Optional[str] = None, layer: Tuple[int, int] = LAYER.LABEL_INSTANCE, ) -> None: """Adds label to a reference in a component.""" instance_name = ( instance_name or f"{reference.parent.name},{int(reference.x)},{int(reference.y)}") component.add_label( text=instance_name, position=pp.drc.snap_to_1nm_grid((reference.x, reference.y)), layer=layer, )
def wg3(length=3, width=0.5): from pp.component import Component c = Component("waveguide") w = width / 2 layer = (1, 0) c.add_polygon([(0, -w), (length, -w), (length, w), (0, w)], layer=layer) c.add_port(name="W0", midpoint=[0, 0], width=width, orientation=180, layer=layer) c.add_port(name="E0", midpoint=[length, 0], width=width, orientation=0, layer=layer) return c
def pad( width: int = 100, height: int = 100, layer: Tuple[int, int] = LAYER.M3 ) -> Component: """rectangular pad with 4 ports (N, S, E, W) Args: width: pad width height: pad height layer: pad layer .. plot:: :include-source: import pp c = pp.c.pad(width=100, height=100, layer=pp.LAYER.M3) pp.plotgds(c) """ c = Component() _c = compass(size=(width, height), layer=layer).ref() c.add(_c) c.absorb(_c) c.ports = _c.ports return c
def connect_electrical_shortest_path(port1, port2): """connects two ports with a polygon that takes the shortest path""" points = [port1.midpoint, port2.midpoint] name = f"zz_conn_{hash_points(points)}" c = Component(name=name) layer = port1.layer p1x0 = port1.endpoints[0][0] p1y0 = port1.endpoints[0][1] p1x1 = port1.endpoints[1][0] p1y1 = port1.endpoints[1][1] p2x0 = port2.endpoints[0][0] p2y0 = port2.endpoints[0][1] p2x1 = port2.endpoints[1][0] p2y1 = port2.endpoints[1][1] if port1.orientation in [90, 270]: c.add_polygon( ([(p1x1, p1y0), (p1x0, p1y1), (p2x1, p2y1), (p2x0, p2y0)]), layer=layer ) else: c.add_polygon( ([(p1x0, p1y1), (p1x1, p1y0), (p2x1, p2y1), (p2x0, p2y0)]), layer=layer ) return c.ref()
def wg_heater_connected(waveguide_heater: Callable = waveguide_heater, wg_heater_connector: Callable = wg_heater_connector, tlm_layers: List[Tuple[int, int]] = [ LAYER.VIA1, LAYER.M1, LAYER.VIA2, LAYER.M2, LAYER.VIA3, LAYER.M3, ], **kwargs) -> Component: """ .. plot:: :include-source: import pp c = pp.c.wg_heater_connected() pp.plotgds(c) """ wg_heater = waveguide_heater(**kwargs) # print(wg_heater.ports.keys()) conn1 = wg_heater_connector( heater_ports=[wg_heater.ports["HBE0"], wg_heater.ports["HTE0"]], tlm_layers=tlm_layers, ) conn2 = wg_heater_connector( heater_ports=[wg_heater.ports["HBW0"], wg_heater.ports["HTW0"]], tlm_layers=tlm_layers, ) cmp = Component() for c in [wg_heater, conn1, conn2]: _c = cmp.add_ref(c) cmp.absorb(_c) for port_name, p in wg_heater.ports.items(): cmp.add_port(name=port_name, port=p) cmp.add_port(name=1, port=conn1.ports["0"]) cmp.add_port(name=2, port=conn2.ports["0"]) cmp.ports[1].orientation = 90 cmp.ports[2].orientation = 90 return cmp
def add_ports_from_markers_square( component: Component, layer: Layer = pp.LAYER.PORTE, port_type: "str" = "dc", orientation: int = 90, min_pin_area_um2: float = 0, pin_extra_width: float = 0.0, port_names: Optional[Iterable[str]] = None, ): """add ports from markers in port_layer adds ports at the marker center Args: component: to read polygons from and to write ports to layer: for port markers port_type: electrical, dc, optical orientation: orientation in degrees 90: north, 0: east, 180: west, 270: south pin_extra_width: 2*offset from pin to waveguide min_pin_area_um2: ignores pins with area smaller than min_pin_area_um2 port_names: names of the ports (defaults to f"{port_type}_{i}") """ port_markers = read_port_markers(component, [layer]) port_names = None or [f"{port_type}_{i}" for i in range(len(port_markers.polygons))] for port_name, p in zip(port_names, port_markers.polygons): dy = snap_to_1nm_grid(p.ymax - p.ymin) dx = snap_to_1nm_grid(p.xmax - p.xmin) x = p.x y = p.y if dx == dy and dx * dy > min_pin_area_um2: component.add_port( port_name, midpoint=(x, y), width=dx - pin_extra_width, orientation=orientation, port_type=port_type, layer=layer, )